W↓
All docs
🔑
Sign Up/Sign In
angular.dev/overview (+36)
Public Link
May 6, 2025, 10:51:48 AM - complete - 2.5 MB
Created by:
****ch@msa.hinet.net
Starting URLs:
https://angular.dev/overview
https://angular.dev/installation
https://angular.dev/essentials
https://angular.dev/tutorials/learn-angular
https://angular.dev/guide/signals
https://angular.dev/guide/components
https://angular.dev/guide/templates
https://angular.dev/guide/directives
https://angular.dev/guide/di
https://angular.dev/guide/routing
https://angular.dev/guide/forms
https://angular.dev/guide/http
https://angular.dev/guide/performance
https://angular.dev/guide/testing
https://angular.dev/guide/i18n
https://angular.dev/guide/experimental/zoneless
https://angular.dev/guide/animations/css
https://angular.dev/tools/cli
https://angular.dev/tools/libraries
https://angular.dev/tools/devtools
https://angular.dev/tools/language-service
https://angular.dev/style-guide
https://angular.dev/best-practices/security
https://angular.dev/best-practices/a11y
https://angular.dev/best-practices/runtime-performance
https://angular.dev/guide/ngmodules/overview
https://angular.dev/guide/animations
https://angular.dev/ecosystem/rxjs-interop
https://angular.dev/ecosystem/service-workers
https://angular.dev/ecosystem/web-workers
https://angular.dev/ecosystem/custom-build-pipeline
https://github.com/angular/angularfire#readme
https://github.com/angular/components/tree/main/src/google-maps#readme
https://github.com/google-pay/google-pay-button#angular
https://github.com/angular/components/blob/main/src/youtube-player/README.md
https://material.angular.dev/cdk/categories
https://material.angular.dev/
Max Pages:
2000
Keep Links:
Yes
## Page: https://angular.dev/overview Angular is a web framework that empowers developers to build fast, reliable applications. Maintained by a dedicated team at Google, Angular provides a broad suite of tools, APIs, and libraries to simplify and streamline your development workflow. Angular gives you a solid platform on which to build fast, reliable applications that scale with both the size of your team and the size of your codebase. **Want to see some code?** Jump over to our [Essentials](https://angular.dev/essentials) for a quick overview of what it's like to use Angular, or get started in the [Tutorial](https://angular.dev/tutorials/learn-angular) if you prefer following step-by-step instructions. ## [Features that power your development](https://angular.dev/#features-that-power-your-development) [ ### Keep your codebase organized with an opinionated component model and flexible dependency injection system Angular components make it easy to split your code into well-encapsulated parts. The versatile dependency injection helps you keep your code modular, loosely-coupled, and testable. Get started with Components](https://angular.dev/guide/components)[ ### Get fast state updates with fine-grained reactivity based on Signals Our fine-grained reactivity model, combined with compile-time optimizations, simplifies development and helps build faster apps by default. Granularly track how and where state is used throughout an application, giving the framework the power to render fast updates via highly optimized instructions. Explore Angular Signals](https://angular.dev/guide/signals)[ ### Meet your performance targets with SSR, SSG, hydration, and next-generation deferred loading Angular supports both server-side rendering (SSR) and static site generation (SSG) along with full DOM hydration. `@defer` blocks in templates make it simple to declaratively divide your templates into lazy-loadable parts. Read about SSR](https://angular.dev/guide/ssr) ### Guarantee everything works together with Angular's first-party modules for forms, routing, and more [Angular's router](https://angular.dev/guide/routing) provides a feature-rich navigation toolkit, including support for route guards, data resolution, lazy-loading, and much more. [Angular's forms module](https://angular.dev/guide/forms) provides a standardized system for form participation and validation. ## [Develop applications faster than ever](https://angular.dev/#develop-applications-faster-than-ever) [ ### Effortlessly build, serve, test, deploy with Angular CLI Angular CLI gets your project running in under a minute with the commands you need to grow into a deployed production application. Angular CLI](https://angular.dev/tools/cli)[ ### Visually debug, analyze, and optimize your code with the Angular DevTools browser extension Angular DevTools sits alongside your browser's developer tools. It helps debug and analyze your app, including a component tree inspector, dependency injection tree view, and custom performance profiling flame chart. Angular DevTools](https://angular.dev/tools/devtools)[ ### Never miss a version with ng update Angular CLI's `ng update` runs automated code transformations that automatically handle routine breaking changes, dramatically simplifying major version updates. Keeping up with the latest version keeps your app as fast and secure as possible. ng update](https://angular.dev/cli/update)[ ### Stay productive with IDE integration in your favorite editor Angular's IDE language services powers code completion, navigation, refactoring, and real-time diagnostics in your favorite editor. Language service](https://angular.dev/tools/language-service) ## [Ship with confidence](https://angular.dev/#ship-with-confidence) ## [Works at any scale](https://angular.dev/#works-at-any-scale) ## [Open-source first](https://angular.dev/#open-source-first) [ ### Courses, blogs and resources Our community is composed of talented developers, writers, instructors, podcasters, and more. The Google for Developers library is just a sample of the high quality resources available for new and experienced developers to continue developing. Check out DevLibrary](https://devlibrary.withgoogle.com/products/angular?sort=added)[ ### Open Source We are thankful for the open source contributors who make Angular a better framework for everyone. From fixing a typo in the docs, to adding major features, we encourage anyone interested to get started on our GitHub. Contribute to Angular](https://github.com/angular/angular/blob/main/CONTRIBUTING.md)[ ### Community partnerships Our team partners with individuals, educators, and enterprises to ensure we consistently are supporting developers. Angular Google Developer Experts (GDEs) represent community leaders around the world educating, organizing, and developing with Angular. Enterprise partnerships help ensure that Angular scales well for technology industry leaders. Meet the Angular GDEs](https://developers.google.com/community/experts/directory?specialization=angular) ### Partnering with other Google technologies Angular partners closely with other Google technologies and teams to improve the web. Our ongoing partnership with Chrome’s Aurora actively explores improvements to user experience across the web, developing built-in performance optimizations like NgOptimizedImage and improvements to Angular’s Core Web Vitals. We are also working with [Firebase](https://firebase.google.com/), [Tensorflow](https://www.tensorflow.org/), [Flutter](https://flutter.dev/), [Material Design](https://m3.material.io/), and [Google Cloud](https://cloud.google.com/) to ensure we provide meaningful integrations across the developer workflow. ### Join the momentum! [Read Angular's roadmap](https://angular.dev/roadmap) [Try out our playground](https://angular.dev/playground) [Learn with tutorials](https://angular.dev/tutorials) [Watch our YouTube course](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF) [Reference our APIs](https://angular.dev/api) --- ## Page: https://angular.dev/installation Get started with Angular quickly with online starters or locally with your terminal. ## [Play Online](https://angular.dev/#play-online) If you just want to play around with Angular in your browser without setting up a project, you can use our online sandbox: ## [Set up a new project locally](https://angular.dev/#set-up-a-new-project-locally) If you're starting a new project, you'll most likely want to create a local project so that you can use tooling such as Git. ### [Prerequisites](https://angular.dev/#prerequisites) * **Node.js** - v[^18.19.1 or newer](https://angular.dev/reference/versions) * **Text editor** - We recommend [Visual Studio Code](https://code.visualstudio.com/) * **Terminal** - Required for running Angular CLI commands * **Development Tool** - To improve your development workflow, we recommend the [Angular Language Service](https://angular.dev/tools/language-service) ### [Instructions](https://angular.dev/#instructions) The following guide will walk you through setting up a local Angular project. #### [Install Angular CLI](https://angular.dev/#install-angular-cli) Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com/), you can open an [integrated terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)) and run the following command: npm install -g @angular/cli pnpm install -g @angular/cli yarn global add @angular/cli bun install -g @angular/cli If you are having issues running this command in Windows or Unix, check out the [CLI docs](https://angular.dev/tools/cli/setup-local#install-the-angular-cli) for more info. #### [Create a new project](https://angular.dev/#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](https://angular.dev/#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 existent for 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! 🎉 ## [Next steps](https://angular.dev/#next-steps) Now that you've created your Angular project, you can learn more about Angular in our [Essentials guide](https://angular.dev/essentials) or choose a topic in our in-depth guides! --- ## Page: https://angular.dev/essentials ## [Interested in Angular?](https://angular.dev/#interested-in-angular) Welcome! This _Essentials_ guide explains some of Angular's main concepts, helping you understand what it's like to use the framework. This guide focuses on just a few building blocks as a short introduction. If you're looking for deep, comprehensive documentation, you can navigate to specific _In-depth Guides_ from the [documentation landing page](https://angular.dev/overview). If you prefer to immediately start writing some code, you can [skip straight to the hands-on tutorial](https://angular.dev/tutorials/learn-angular). ### [Before you start](https://angular.dev/#before-you-start) This site expects that you're familiar with HTML, CSS and TypeScript. If you are totally new to web development, you should seek out some more introductory content before coming back here. In particular, you should be familiar with the following concepts: * [JavaScript Classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes) * [TypeScript fundamentals](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) * [TypeScript Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) ## [Next Step](https://angular.dev/#next-step) Ready to jump in? It's time to learn about components in Angular! [Composing with Components](https://angular.dev/essentials/components) --- ## Page: https://angular.dev/essentials/components 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](https://angular.dev/#defining-a-component) Every component has a few main parts: 1. A `@Component`[decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that contains some configuration used by Angular. 2. An HTML template that controls what renders into the DOM. 3. A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML. 4. A TypeScript class with behaviors, such as handling user input or making requests to a server. Here is a simplified example of a `UserProfile` component. // user-profile.ts@Component({ selector: 'user-profile', template: ` <h1>User profile</h1> <p>This is the user profile page</p> `,})export class UserProfile { /* Your component code goes here */ } The `@Component` 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](https://angular.dev/#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>Use profile</h1><p>This is the user profile page</p> /* user-profile.css */h1 { font-size: 3em;} ## [Using components](https://angular.dev/#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: UserProfile UserBiography ProfilePhoto UserAddress 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.tsimport {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](https://angular.dev/guide/components) for the full details. ## [Next Step](https://angular.dev/#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. [Reactivity with signals](https://angular.dev/essentials/signals) [In-depth components guide](https://angular.dev/guide/components) --- ## Page: https://angular.dev/essentials/signals 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](https://angular.dev/#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()); // MORGANfirstName.set('Jaime');console.log(firstNameCapitalized()); // JAIME ## [Using signals in components](https://angular.dev/#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](https://angular.dev/guide/signals) for the full details. ## [Next Step](https://angular.dev/#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. [Dynamic interfaces with templates](https://angular.dev/essentials/templates) [In-depth signals guide](https://angular.dev/guide/signals) --- ## Page: https://angular.dev/essentials/templates 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](https://angular.dev/#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](https://angular.dev/#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 attribute when the bound value changes. ## [Handling user interaction](https://angular.dev/#handling-user-interaction) Angular lets you add event listeners to an element in your template with parentheses: @Component({ /*...*/ // Add an 'click' event handler that calls the `cancelSubscription` method. template: `<button (click)="cancelSubscription()">Cancel subscription</button>`,})export class UserProfile { /* ... */ cancelSubscription() { /* Your event handling code goes here. */ }} If you need to pass the [event](https://developer.mozilla.org/docs/Web/API/Event) object to your listener, you can use Angular's built-in `$event` variable inside the function call: @Component({ /*...*/ // Add an 'click' event handler that calls the `cancelSubscription` method. template: `<button (click)="cancelSubscription($event)">Cancel subscription</button>`,})export class UserProfile { /* ... */ cancelSubscription(event: Event) { /* Your event handling code goes here. */ }} ## [Control flow with `@if` and `@for`](https://angular.dev/#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?_](https://angular.dev/guide/templates/control-flow#why-is-track-in-for-blocks-important) for more info. **TIP:** Want to know more about Angular templates? See the [In-depth Templates guide](https://angular.dev/guide/templates) for the full details. ## [Next Step](https://angular.dev/#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. [Modular design with dependency injection](https://angular.dev/essentials/dependency-injection) [In-depth template guide](https://angular.dev/guide/templates) --- ## Page: https://angular.dev/essentials/dependency-injection When you need to share logic between components, Angular leverages the design pattern of [dependency injection](https://angular.dev/guide/di) that allows you to create a “service” which allows you to inject code into components while managing it from a single source of truth. ## [What are services?](https://angular.dev/#what-are-services) Services are reusable pieces of code that can be injected. Similar to defining a component, services are comprised of the following: * A **TypeScript decorator** that declares the class as an Angular service via `@Injectable` and allows you to define what part of the application can access the service via the `providedIn` property (which is typically `'root'`) to allow a service to be accessed anywhere within the application. * A **TypeScript class** that defines the desired code that will be accessible when the service is injected Here is an example of a `Calculator` service. import {Injectable} from '@angular/core';@Injectable({providedIn: 'root'})export class Calculator { add(x: number, y: number) { return x + y; }} ## [How to use a service](https://angular.dev/#how-to-use-a-service) When you want to use a service in a component, you need to: 1. Import the service 2. Declare a class field where the service is injected. Assign the class field to the result of the call of the built-in function `inject` which creates the service Here’s what it might look like in the `Receipt` component: import { Component, inject } from '@angular/core';import { Calculator } from './calculator';@Component({ selector: 'app-receipt', template: `<h1>The total is {{ totalCost }}</h1>`,})export class Receipt { private calculator = inject(Calculator); totalCost = this.calculator.add(50, 25);} In this example, the `Calculator` is being used by calling the Angular function `inject` and passing in the service to it. ## [Next Step](https://angular.dev/#next-step) [Next Steps After Essentials](https://angular.dev/essentials/next-steps) [In-depth dependency injection guide](https://angular.dev/guide/di) --- ## Page: https://angular.dev/essentials/next-steps Now that you have been introduced to main concepts of Angular - you're ready to put what you learned into practices with our interactive tutorials and learn more with our in-depth guides. ## [Playground](https://angular.dev/#playground) Try out Angular in an interactive code editor to further explore the concepts you've learned. [Play with Angular!](https://angular.dev/playground) ## [Tutorials](https://angular.dev/#tutorials) Put these main concepts into practice with our in-browser tutorial or build your first app locally with the Angular CLI. [Learn Angular's fundamentals](https://angular.dev/tutorials/learn-angular) [Build your first Angular app](https://angular.dev/tutorials/first-app) ## [In-depth Guides](https://angular.dev/#in-depth-guides) Here are some in-depth guides you might be interested in reading: [Components In-depth Guide](https://angular.dev/guide/components/importing) [Template In-depth Guide](https://angular.dev/guide/templates) [Forms In-depth Guide](https://angular.dev/guide/forms) To see the rest of our in-depth guides, check out the main navigation. --- ## Page: https://angular.dev/essentials/guide/components ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/essentials/signals ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/guide/signals ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/essentials/templates ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/guide/templates/control-flow#why-is-track-in-for-blocks-important ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/guide/templates ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/essentials/dependency-injection ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/essentials/next-steps ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/playground ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/tutorials/learn-angular ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/tutorials/first-app ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/essentials/guide/components/importing ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular ## Welcome to the Angular tutorial [](https://github.com/angular/angular/edit/main/adev/src/content/tutorials/learn-angular/intro/README.md "Edit this page") This interactive tutorial will teach you the basic building blocks to start building great apps with Angular. ## [How to use this tutorial](https://angular.dev/#how-to-use-this-tutorial) You'll need to have basic familiarity with HTML, CSS and JavaScript to understand Angular. Each step represents a concept in Angular. You can do one, or all of them. If you get stuck, click "Reveal answer" at the top. Alright, let's [get started](https://angular.dev/tutorials/learn-angular/1-components-in-angular). --- ## Page: https://angular.dev/tutorials/learn-angular/1-components-in-angular Components are the foundational building blocks for any Angular application. Each component has three parts: * TypeScript class * HTML template * CSS styles Note: Learn more about [components in the essentials guide](https://angular.dev/essentials/components). In this activity, you'll learn how to update the template and styles of a component. * * * This is a great opportunity for you to get started with Angular. 1. ### [Update the component template](https://angular.dev/#update-the-component-template) Update the `template` property to read `Hello Universe` template: ` Hello Universe`, When you changed the HTML template, the preview updated with your message. Let's go one step further: change the color of the text. 2. ### [Update the component styles](https://angular.dev/#update-the-component-styles) Update the styles value and change the `color` property from `blue` to `#a144eb`. styles: ` :host { color: #a144eb; }`, When you check the preview, you'll find that the text color will be changed. In Angular, you can use all the browser supported CSS and HTML that's available. If you'd like, you can store your template and styles in separate files. --- ## Page: https://angular.dev/tutorials/learn-angular/2-updating-the-component-class In Angular, the component's logic and behavior are defined in the component's TypeScript class. Note: Learn more about [showing dynamic text in the essentials guide](https://angular.dev/essentials/templates#showing-dynamic-text). In this activity, you'll learn how to update the component class and how to use [interpolation](https://angular.dev/guide/templates/binding#render-dynamic-text-with-text-interpolation). * * * 1. ### [Add a property called `city`](https://angular.dev/#add-a-property-called-city) Update the component class by adding a property called `city` to the `AppComponent` class. export class AppComponent { city = 'San Francisco';} The `city` property is of type `string` but you can omit the type because of [type inference in TypeScript](https://www.typescriptlang.org/docs/handbook/type-inference.html). The `city` property can be used in the `AppComponent` class and can be referenced in the component template. To use a class property in a template, you have to use the `{{ }}` syntax. 2. ### [Update the component template](https://angular.dev/#update-the-component-template) Update the `template` property to match the following HTML: template: `Hello {{ city }}`, This is an example of interpolation and is a part of Angular template syntax. It enables you to do much more than put dynamic text in a template. You can also use this syntax to call functions, write expressions and more. 3. ### [More practice with interpolation](https://angular.dev/#more-practice-with-interpolation) Try this - add another set of `{{ }}` with the contents being `1 + 1`: template: `Hello {{ city }}, {{ 1 + 1 }}`, Angular evaluates the contents of the `{{ }}` and renders the output in the template. This is just the beginning of what's possible with Angular templates, keep on learning to find out more. --- ## Page: https://angular.dev/tutorials/learn-angular/3-composing-components You've learned to update the component template, component logic, and component styles, but how do you use a component in your application? The `selector` property of the component configuration gives you a name to use when referencing the component in another template. You use the `selector` like an HTML tag, for example `app-user` would be `<app-user />` in the template. Note: Learn more about [using components in the essentials guide](https://angular.dev/essentials/components#using-components). In this activity, you'll learn how to compose components. * * * In this example, there are two components `UserComponent` and `AppComponent`. 1. ### [Add a reference to `UserComponent`](https://angular.dev/#add-a-reference-to-usercomponent) Update the `AppComponent` template to include a reference to the `UserComponent` which uses the selector `app-user`. Be sure to add `UserComponent` to the imports array of `AppComponent`, this makes it available for use in the `AppComponent` template. template: `<app-user />`,imports: [UserComponent] The component now displays the message `Username: youngTech`. You can update the template code to include more markup. 2. ### [Add more markup](https://angular.dev/#add-more-markup) Because you can use any HTML markup that you want in a template, try updating the template for `AppComponent` to also include more HTML elements. This example will add a `<section>` element as the parent of the `<app-user>` element. template: `<section><app-user /></section>`, You can use as much HTML markup and as many components as you need to bring your app idea to reality. You can even have multiple copies of your component on the same page. That's a great segue, how would you conditionally show a component based on data? Head to the next section to find out. --- ## Page: https://angular.dev/tutorials/learn-angular/4-control-flow-if Deciding what to display on the screen for a user is a common task in application development. Many times, the decision is made programmatically using conditions. To express conditional displays in templates, Angular uses the `@if` template syntax. Note: Learn more about [control flow in the essentials guide](https://angular.dev/essentials/templates#control-flow-with-if-and-for). In this activity, you'll learn how to use conditionals in templates. * * * The syntax that enables the conditional display of elements in a template is `@if`. Here's an example of how to use the `@if` syntax in a component: @Component({ ... template: ` @if (isLoggedIn) { <p>Welcome back, Friend!</p> } `,})class AppComponent { isLoggedIn = true;} Two things to take note of: * There is an `@` prefix for the `if` because it is a special type of syntax called [Angular template syntax](https://angular.dev/guide/templates) * For applications using v16 and older please refer to the [Angular documentation for NgIf](https://angular.dev/guide/directives/structural-directives) for more information. 1. ### [Create a property called `isServerRunning`](https://angular.dev/#create-a-property-called-isserverrunning) In the `AppComponent` class, add a `boolean` property called `isServerRunning`, set the initial value to `true`. 2. ### [Use `@if` in the template](https://angular.dev/#use-if-in-the-template) Update the template to display the message `Yes, the server is running` if the value of `isServerRunning` is `true`. 3. ### [Use `@else` in the template](https://angular.dev/#use-else-in-the-template) Now Angular supports native template syntax for defining the else case with the `@else` syntax. Update the template to display the message `No, the server is not running` as the else case. Here's an example: template: ` @if (isServerRunning) { ... } @else { ... }`; Add your code to fill in the missing markup. This type of functionality is called conditional control flow. Next you'll learn how to repeat items in a template. --- ## Page: https://angular.dev/tutorials/learn-angular/5-control-flow-for Often when building web applications, you need to repeat some code a specific number of times - for example, given an array of names, you may want to display each name in a `<p>` tag. Note: Learn more about [control flow in the essentials guide](https://angular.dev/essentials/templates#control-flow-with-if-and-for). In this activity, you'll learn how to use `@for` to repeat elements in a template. * * * The syntax that enables repeating elements in a template is `@for`. Here's an example of how to use the `@for` syntax in a component: @Component({ ... template: ` @for (os of operatingSystems; track os.id) { {{ os.name }} } `,})export class AppComponent { operatingSystems = [{id: 'win', name: 'Windows'}, {id: 'osx', name: 'MacOS'}, {id: 'linux', name: 'Linux'}];} Two things to take note of: * There is an `@` prefix for the `for` because it is a special syntax called [Angular template syntax](https://angular.dev/guide/templates) * For applications using v16 and older please refer to the [Angular documentation for NgFor](https://angular.dev/guide/directives/structural-directives) 1. ### [Add the `users` property](https://angular.dev/#add-the-users-property) In the `AppComponent` class, add a property called `users` that contains users and their names. [{id: 0, name: 'Sarah'}, {id: 1, name: 'Amy'}, {id: 2, name: 'Rachel'}, {id: 3, name: 'Jessica'}, {id: 4, name: 'Poornima'}] 2. ### [Update the template](https://angular.dev/#update-the-template) Update the template to display each user name in a `p` element using the `@for` template syntax. @for (user of users; track user.id) { <p>{{ user.name }}</p>} NOTE: the use of `track` is required, you may use the `id` or some other unique identifier. This type of functionality is called control flow. Next, you'll learn to customize and communicate with components - by the way, you're doing a great job so far. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/templates ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/directives/structural-directives ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/6-property-binding Property binding in Angular enables you to set values for properties of HTML elements, Angular components and more. Use property binding to dynamically set values for properties and attributes. You can do things such as toggle button features, set image paths programmatically, and share values between components. Note: Learn more about [setting dynamic properties and attributes in the essentials guide](https://angular.dev/essentials/templates#setting-dynamic-properties-and-attributes). In this activity, you'll learn how to use property binding in templates. * * * To bind to an element's attribute, wrap the attribute name in square brackets. Here's an example: <img alt="photo" [src]="imageURL"> In this example, the value of the `src` attribute will be bound to the class property `imageURL`. Whatever value `imageURL` has will be set as the `src` attribute of the `img` tag. 1. ### [Add a property called `isEditable`](https://angular.dev/#add-a-property-called-iseditable) Update the code in `app.component.ts` by adding a property to the `AppComponent` class called `isEditable` with the initial value set to `true`. export class AppComponent { isEditable = true;} 2. ### [Bind to `contentEditable`](https://angular.dev/#bind-to-contenteditable) Next, bind the `contentEditable` attribute of the `div` to the `isEditable` property by using the `[]` syntax. @Component({ ... template: `<div [contentEditable]="isEditable"></div>`,}) The div is now editable. Nice work 👍 Property binding is one of Angular's many powerful features. If you'd like to learn more checkout [the Angular documentation](https://angular.dev/guide/templates/property-binding). --- ## Page: https://angular.dev/tutorials/learn-angular/7-event-handling Event handling enables interactive features on web apps. It gives you the ability as a developer to respond to user actions like button presses, form submissions and more. Note: Learn more about [handling user interaction in the essentials guide](https://angular.dev/essentials/templates#handling-user-interaction). In this activity, you'll learn how to add an event handler. * * * In Angular you bind to events with the parentheses syntax `()`. On a given element, wrap the event you want to bind to with parentheses and set an event handler. Consider this `button` example: @Component({ ... template: `<button (click)="greet()">`})class AppComponent { greet() { console.log('Hello, there 👋'); }} In this example, the `greet()` function will run every time the button is clicked. Take note that the `greet()` syntax includes the trailing parenthesis. Alright, your turn to give this a try: 1. ### [Add an event handler](https://angular.dev/#add-an-event-handler) Add the `onMouseOver` event handler function in the `AppComponent` class. Use the following code as the implementation: onMouseOver() { this.message = 'Way to go 🚀';} 2. ### [Bind to the template event](https://angular.dev/#bind-to-the-template-event) Update the template code in `app.component.ts` to bind to the `mouseover` event of the `section` element. <section (mouseover)="onMouseOver()"> And with a few steps in the code you've created your first event handler in Angular. Seems like you are getting pretty good at this, keep up the good work. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/templates/property-binding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/8-input Sometimes app development requires you to send data into a component. This data can be used to customize a component or perhaps send information from a parent component to a child component. Angular uses a concept called `Input`. This is similar to `props` in other frameworks. To create an `Input` property, use the `@Input` decorator. Note: Learn more about [accepting data with input properties in the inputs guide](https://angular.dev/guide/components/inputs). In this activity, you'll learn how to use the `@Input` decorator to send information to components. * * * To create an `Input` property, add the `@Input` decorator to a property of a component class: class UserComponent { @Input() occupation = '';} When you are ready to pass in a value through an `Input`, values can be set in templates using the attribute syntax. Here's an example: @Component({ ... template: `<app-user occupation="Angular Developer"></app-user>`})class AppComponent {} Make sure you bind the property `occupation` in your `UserComponent`. @Component({ ... template: `<p>The user's occupation is {{occupation}}</p>`}) 1. ### [Define an `@Input` property](https://angular.dev/#define-an-input-property) Update the code in `user.component.ts` to define an `Input` property on the `UserComponent` called `name`. For now, set the initial value to `empty string`. Be sure to update the template to interpolate the `name` property at the end of the sentence. 2. ### [Pass a value to the `@Input` property](https://angular.dev/#pass-a-value-to-the-input-property) Update the code in `app.component.ts` to send in the `name` property with a value of `"Simran"`. When the code has been successfully updated, the app will display `The user's name is Simran`. While this is great, it is only one direction of the component communication. What if you want to send information and data to a parent component from a child component? Check out the next lesson to find out. P.S. you are doing great - keep going 🎉 --- ## Page: https://angular.dev/tutorials/learn-angular/9-output When working with components it may be required to notify other components that something has happened. Perhaps a button has been clicked, an item has been added/removed from a list or some other important update has occurred. In this scenario components need to communicate with parent components. Angular uses the `@Output` decorator to enable this type of behavior. Note: Learn more about [custom events in the outputs guide](https://angular.dev/guide/components/outputs). In this activity, you'll learn how to use the `@Output` decorator and `EventEmitter` to communicate with components. * * * To create the communication path from child to parent components, use the `@Output` decorator on a class property and assign it a value of type `EventEmitter`: @Component({...})class ChildComponent { @Output() incrementCountEvent = new EventEmitter<number>();} Now the component can generate events that can be listened to by the parent component. Trigger events by calling the `emit` method: class ChildComponent { ... onClick() { this.count++; this.incrementCountEvent.emit(this.count); }} The emit function will generate an event with the same type as the `EventEmitter` instance. Alright, your turn to give this a try. Complete the code by following these tasks: 1. ### [Add an `@Output` property](https://angular.dev/#add-an-output-property) Update `child.component.ts` by adding an output property called `addItemEvent`, be sure to set the EventEmitter type to be `string`. 2. ### [Complete `addItem` method](https://angular.dev/#complete-additem-method) In `child.component.ts` update the `addItem` method; use the following code as the logic: addItem() { this.addItemEvent.emit('🐢');} 3. ### [Update the `AppComponent` template](https://angular.dev/#update-the-appcomponent-template) In `app.component.ts` update the template to listen to the emitted event by adding the following code: <app-child (addItemEvent)="addItem($event)" /> Now, the "Add Item" button adds a new item to the list every time the button is clicked. Wow, at this point you've completed the component fundamentals - impressive 👏 Keep learning to unlock more of Angular's great features. --- ## Page: https://angular.dev/tutorials/learn-angular/10-deferrable-views Sometimes in app development, you end up with a lot of components that you need to reference in your app, but some of those don't need to be loaded right away for various reasons. Maybe they are below the visible fold or are heavy components that aren't interacted with until later. In that case, we can load some of those resources later with deferrable views. Note: Learn more about [deferred loading with @defer in the in-depth guide](https://angular.dev/guide/templates/defer). In this activity, you'll learn how to use deferrable views to defer load a section of your component template. * * * 1. In your app, the blog post page has a comment component after the post details. Wrap the comment component with a `@defer` block to defer load it. @defer { <comments />} The code above is an example of how to use a basic `@defer` block. By default `@defer` will load the `comments` component when the browser is idle. 2. ### [Add a placeholder](https://angular.dev/#add-a-placeholder) Add a `@placeholder` block to the `@defer` block. The `@placeholder` block is where you put html that will show before the deferred loading starts. The content in `@placeholder` blocks is eagerly loaded. @defer { <comments />} @placeholder { <p>Future comments</p>} 3. ### [Add a loading block](https://angular.dev/#add-a-loading-block) Add a `@loading` block to the `@defer` block. The `@loading` block is where you put html that will show _while_ the deferred content is actively being fetched, but hasn't finished yet. The content in `@loading` blocks is eagerly loaded. @defer { <comments />} @placeholder { <p>Future comments</p>} @loading { <p>Loading comments...</p>} 4. ### [Add a minimum duration](https://angular.dev/#add-a-minimum-duration) Both `@placeholder` and `@loading` sections have optional parameters to prevent flickering from occurring when loading happens quickly. `@placeholder` has `minimum` and `@loading` has `minimum` and `after`. Add a `minimum` duration to the `@loading` block so it will be rendered for at least 2 seconds. @defer { <comments />} @placeholder { <p>Future comments</p>} @loading (minimum 2s) { <p>Loading comments...</p>} 5. ### [Add a viewport trigger](https://angular.dev/#add-a-viewport-trigger) Deferrable views have a number of trigger options. Add a viewport trigger so the content will defer load once it enters the viewport. @defer (on viewport) { <comments />} 6. ### [Add content](https://angular.dev/#add-content) A viewport trigger is best used when you're deferring content that's far enough down the page that it needs to be scrolled to see. So let's add some content to our blog post. You can either write your own, or you can copy the content below and put it inside the `<article>` element. <article> <p>Angular is my favorite framework, and this is why. Angular has the coolest deferrable view feature that makes defer loading content the easiest and most ergonomic it could possibly be. The Angular community is also filled with amazing contributors and experts that create excellent content. The community is welcoming and friendly, and it really is the best community out there.</p> <p>I can't express enough how much I enjoy working with Angular. It offers the best developer experience I've ever had. I love that the Angular team puts their developers first and takes care to make us very happy. They genuinely want Angular to be the best framework it can be, and they're doing such an amazing job at it, too. This statement comes from my heart and is not at all copied and pasted. In fact, I think I'll say these exact same things again a few times.</p> <p>Angular is my favorite framework, and this is why. Angular has the coolest deferrable view feature that makes defer loading content the easiest and most ergonomic it could possibly be. The Angular community is also filled with amazing contributors and experts that create excellent content. The community is welcoming and friendly, and it really is the best community out there.</p> <p>I can't express enough how much I enjoy working with Angular. It offers the best developer experience I've ever had. I love that the Angular team puts their developers first and takes care to make us very happy. They genuinely want Angular to be the best framework it can be, and they're doing such an amazing job at it, too. This statement comes from my heart and is not at all copied and pasted. In fact, I think I'll say these exact same things again a few times.</p> <p>Angular is my favorite framework, and this is why. Angular has the coolest deferrable view feature that makes defer loading content the easiest and most ergonomic it could possibly be. The Angular community is also filled with amazing contributors and experts that create excellent content. The community is welcoming and friendly, and it really is the best community out there.</p> <p>I can't express enough how much I enjoy working with Angular. It offers the best developer experience I've ever had. I love that the Angular team puts their developers first and takes care to make us very happy. They genuinely want Angular to be the best framework it can be, and they're doing such an amazing job at it, too. This statement comes from my heart and is not at all copied and pasted.</p></article> Once you've added this code, now scroll down to see the deferred content load once you scroll it into the viewport. In the activity, you've learned how to use deferrable views in your applications. Great work. 🙌 There's even more you can do with them, like different triggers, prefetching, and `@error` blocks. If you would like to learn more, check out the [documentation for Deferrable views](https://angular.dev/guide/defer). --- ## Page: https://angular.dev/tutorials/learn-angular/11-optimizing-images Images are a big part of many applications, and can be a major contributor to application performance problems, including low [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals) scores. Image optimization can be a complex topic, but Angular handles most of it for you, with the `NgOptimizedImage` directive. Note: Learn more about [image optimization with NgOptimizedImage in the in-depth guide](https://angular.dev/guide/image-optimization). In this activity, you'll learn how to use `NgOptimizedImage` to ensure your images are loaded efficiently. * * * 1. ### [Import the NgOptimizedImage directive](https://angular.dev/#import-the-ngoptimizedimage-directive) In order to leverage the `NgOptimizedImage` directive, first import it from the `@angular/common` library and add it to the component `imports` array. import { NgOptimizedImage } from '@angular/common';@Component({ imports: [NgOptimizedImage], ...}) 2. ### [Update the src attribute to be ngSrc](https://angular.dev/#update-the-src-attribute-to-be-ngsrc) To enable the `NgOptimizedImage` directive, swap out the `src` attribute for `ngSrc`. This applies for both static image sources (i.e., `src`) and dynamic image sources (i.e., `[src]`). import { NgOptimizedImage } from '@angular/common';@Component({template: ` ... <li> Static Image: <img ngSrc="/assets/logo.svg" alt="Angular logo" width="32" height="32" /> </li> <li> Dynamic Image: <img [ngSrc]="logoUrl" [alt]="logoAlt" width="32" height="32" /> </li> ... `,imports: [NgOptimizedImage],}) 3. ### [Add width and height attributes](https://angular.dev/#add-width-and-height-attributes) Note that in the above code example, each image has both `width` and `height` attributes. In order to prevent [layout shift](https://web.dev/articles/cls), the `NgOptimizedImage` directive requires both size attributes on each image. In situations where you can't or don't want to specify a static `height` and `width` for images, you can use [the `fill` attribute](https://web.dev/articles/cls) to tell the image to act like a "background image", filling its containing element: <div class="image-container"> //Container div has 'position: "relative"' <img ngSrc="www.example.com/image.png" fill /></div> NOTE: For the `fill` image to render properly, its parent element must be styled with `position: "relative"`, `position: "fixed"`, or `position: "absolute"`. 4. ### [Prioritize important images](https://angular.dev/#prioritize-important-images) One of the most important optimizations for loading performance is to prioritize any image which might be the ["LCP element"](https://web.dev/articles/optimize-lcp), which is the largest on-screen graphical element when the page loads. To optimize your loading times, make sure to add the `priority` attribute to your "hero image" or any other images that you think could be an LCP element. <img ngSrc="www.example.com/image.png" height="600" width="800" priority /> 5. ### [Optional: Use an image loader](https://angular.dev/#optional-use-an-image-loader) `NgOptimizedImage` allows you to specify an [image loader](https://angular.dev/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage), which tells the directive how to format URLs for your images. Using a loader allows you to define your images with short, relative URLs: providers: [ provideImgixLoader('https://my.base.url/'),] Final URL will be '[https://my.base.url/image.png](https://my.base.url/image.png)' <img ngSrc="image.png" height="600" width="800" /> Image loaders are for more than just convenience--they allow you to use the full capabilities of `NgOptimizedImage`. Learn more about these optimizations and the built-in loaders for popular CDNs [here](https://angular.dev/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage). By adding this directive to your workflow, your images are now loading using best practices with the help of Angular 🎉 If you would like to learn more, check out the [documentation for `NgOptimizedImage`](https://angular.dev/guide/image-optimization). Keep up the great work and let's learn about routing next. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/defer ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/12-enable-routing For most apps, there comes a point where the app requires more than a single page. When that time inevitably comes, routing becomes a big part of the performance story for users. Note: Learn more about [routing in the in-depth guide](https://angular.dev/guide/routing). In this activity, you'll learn how to set up and configure your app to use Angular Router. * * * 1. ### [Create an app.routes.ts file](https://angular.dev/#create-an-approutests-file) Inside `app.routes.ts`, make the following changes: 1. Import `Routes` from the `@angular/router` package. 2. Export a constant called `routes` of type `Routes`, assign it `[]` as the value. import {Routes} from '@angular/router';export const routes: Routes = []; 2. ### [Add routing to provider](https://angular.dev/#add-routing-to-provider) In `app.config.ts`, configure the app to Angular Router with the following steps: 1. Import the `provideRouter` function from `@angular/router`. 2. Import `routes` from the `./app.routes.ts`. 3. Call the `provideRouter` function with `routes` passed in as an argument in the `providers` array. import {ApplicationConfig} from '@angular/core';import {provideRouter} from '@angular/router';import {routes} from './app.routes';export const appConfig: ApplicationConfig = {providers: [provideRouter(routes)],}; 3. ### [Import `RouterOutlet` in the component](https://angular.dev/#import-routeroutlet-in-the-component) Finally, to make sure your app is ready to use the Angular Router, you need to tell the app where you expect the router to display the desired content. Accomplish that by using the `RouterOutlet` directive from `@angular/router`. Update the template for `AppComponent` by adding `<router-outlet />` import {RouterOutlet} from '@angular/router';@Component({...template: ` <nav> <a href="/">Home</a> | <a href="/user">User</a> </nav> <router-outlet /> `,imports: [RouterOutlet],})export class AppComponent {} Your app is now set up to use Angular Router. Nice work! 🙌 Keep the momentum going to learn the next step of defining the routes for our app. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/13-define-a-route Now that you've set up the app to use Angular Router, you need to define the routes. Note: Learn more about [defining a basic route in the in-depth guide](https://angular.dev/guide/routing/common-router-tasks#defining-a-basic-route). In this activity, you'll learn how to add and configure routes with your app. * * * 1. ### [Define a route in `app.routes.ts`](https://angular.dev/#define-a-route-in-approutests) In your app, there are two pages to display: (1) Home Page and (2) User Page. To define a route, add a route object to the `routes` array in `app.routes.ts` that contains: * The `path` of the route (which automatically starts at the root path (i.e., `/`)) * The `component` that you want the route to display import {Routes} from '@angular/router';import {HomeComponent} from './home/home.component';export const routes: Routes = [ { path: '', component: HomeComponent, },]; The code above is an example of how `HomeComponent` can be added as a route. Now go ahead and implement this along with the `UserComponent` in the playground. Use `'user'` for the path of `UserComponent`. 2. ### [Add title to route definition](https://angular.dev/#add-title-to-route-definition) In addition to defining the routes correctly, Angular Router also enables you to set the page title whenever users are navigating by adding the `title` property to each route. In `app.routes.ts`, add the `title` property to the default route (`path: ''`) and the `user` route. Here's an example: import {Routes} from '@angular/router';import {HomeComponent} from './home/home.component';export const routes: Routes = [{path: '',title: 'App Home Page',component: HomeComponent,},]; In the activity, you've learned how to define and configure routes in your Angular app. Nice work. 🙌 The journey to fully enabling routing in your app is almost complete, keep going. --- ## Page: https://angular.dev/tutorials/learn-angular/14-routerLink In the app's current state, the entire page refreshes when we click on an internal link that exists within the app. While this may not seem significant with a small app, this can have performance implications for larger pages with more content where users have to redownload assets and run calculations again. Note: Learn more about [adding routes to your application in the in-depth guide](https://angular.dev/guide/routing/common-router-tasks#add-your-routes-to-your-application). In this activity, you'll learn how to leverage the `RouterLink` directive to make the most use of Angular Router. * * * 1. ### [Import `RouterLink` directive](https://angular.dev/#import-routerlink-directive) In `app.component.ts` add the `RouterLink` directive import to the existing import statement from `@angular/router` and add it to the `imports` array of your component decorator. ...import { RouterLink, RouterOutlet } from '@angular/router';@Component({ imports: [RouterLink, RouterOutlet], ...}) 2. ### [Add a `routerLink` to template](https://angular.dev/#add-a-routerlink-to-template) To use the `RouterLink` directive, replace the `href` attributes with `routerLink`. Update the template with this change. import { RouterLink, RouterOutlet } from '@angular/router';@Component({ ... template: ` ... <a routerLink="/">Home</a> <a routerLink="/user">User</a> ... `, imports: [RouterLink, RouterOutlet],}) When you click on the links in the navigation now, you should not see any blinking and only the content of the page itself (i.e., `router-outlet`) being changed 🎉 Great job learning about routing with Angular. This is just the surface of the `Router` API, to learn more check out the [Angular Router Documentation](https://angular.dev/guide/routing). --- ## Page: https://angular.dev/tutorials/learn-angular/15-forms Forms are a big part of many apps because they enable your app to accept user input. Let's learn about how forms are handled in Angular. In Angular, there are two types of forms: template-driven and reactive. You'll learn about both over the next few activities. Note: Learn more about [forms in Angular in the in-depth guide](https://angular.dev/guide/forms). In this activity, you'll learn how to set up a form using a template-driven approach. * * * 1. ### [Create an input field](https://angular.dev/#create-an-input-field) In `user.component.ts`, update the template by adding a text input with the `id` set to `framework`, type set to `text`. <label for="framework"> Favorite Framework: <input id="framework" type="text" /></label> 2. ### [Import `FormsModule`](https://angular.dev/#import-formsmodule) For this form to use Angular features that enable data binding to forms, you'll need to import the `FormsModule`. Import the `FormsModule` from `@angular/forms` and add it to the `imports` array of the `UserComponent`. import {Component} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({...imports: [FormsModule],})export class UserComponent {} 3. ### [Add binding to the value of the input](https://angular.dev/#add-binding-to-the-value-of-the-input) The `FormsModule` has a directive called `ngModel` that binds the value of the input to a property in your class. Update the input to use the `ngModel` directive, specifically with the following syntax `[(ngModel)]="favoriteFramework"` to bind to the `favoriteFramework` property. <label for="framework"> Favorite Framework: <input id="framework" type="text" [(ngModel)]="favoriteFramework" /></label> After you've made changes, try entering a value in the input field. Notice how it updates on the screen (yes, very cool). NOTE: The syntax `[()]` is known as "banana in a box" but it represents two-way binding: property binding and event binding. Learn more in the [Angular docs about two-way data binding](https://angular.dev/guide/templates/two-way-binding). You've now taken an important first step towards building forms with Angular. Nice work. Let's keep the momentum going! --- ## Page: https://angular.dev/tutorials/learn-angular/guide/routing ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/16-form-control-values Now that your forms are set up with Angular, the next step is to access the values from the form controls. Note: Learn more about [adding a basic form control in the in-depth guide](https://angular.dev/guide/forms/reactive-forms#adding-a-basic-form-control). In this activity, you'll learn how to get the value from your form input. * * * 1. ### [Show the value of the input field in the template](https://angular.dev/#show-the-value-of-the-input-field-in-the-template) To display the input value in a template, you can use the interpolation syntax `{{}}` just like any other class property of the component: @Component({ selector: 'app-user', template: ` ... <p>Framework: {{ favoriteFramework }}</p> <label for="framework"> Favorite Framework: <input id="framework" type="text" [(ngModel)]="favoriteFramework" /> </label> `,})export class UserComponent { favoriteFramework = '';} 2. ### [Retrieve the value of an input field](https://angular.dev/#retrieve-the-value-of-an-input-field) When you need to reference the input field value in the component class, you can do so by accessing the class property with the `this` syntax. ...@Component({ selector: 'app-user', template: ` ... <button (click)="showFramework()">Show Framework</button> `, ...})export class UserComponent { favoriteFramework = ''; ...showFramework() {alert(this.favoriteFramework);}} Great job learning how to display the input values in your template and access them programmatically. Time to progress onto the next way of managing forms with Angular: reactive forms. If you'd like to learn more about template-driven forms, please refer to the [Angular forms documentation](https://angular.dev/guide/forms/template-driven-forms). --- ## Page: https://angular.dev/tutorials/learn-angular/guide/templates/two-way-binding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/17-reactive-forms When you want to manage your forms programmatically instead of relying purely on the template, reactive forms are the answer. Note: Learn more about [reactive forms in the in-depth guide](https://angular.dev/guide/forms/reactive-forms). In this activity, you'll learn how to set up reactive forms. * * * 1. ### [Import `ReactiveForms` module](https://angular.dev/#import-reactiveforms-module) In `app.component.ts`, import `ReactiveFormsModule` from `@angular/forms` and add it to the `imports` array of the component. import { ReactiveFormsModule } from '@angular/forms';@Component({ selector: 'app-root', template: ` <form> <label>Name <input type="text" /> </label> <label>Email <input type="email" /> </label> <button type="submit">Submit</button> </form> `, imports: [ReactiveFormsModule],}) 2. ### [Create the `FormGroup` object with FormControls](https://angular.dev/#create-the-formgroup-object-with-formcontrols) Reactive forms use the `FormControl` class to represent the form controls (e.g., inputs). Angular provides the `FormGroup` class to serve as a grouping of form controls into a helpful object that makes handling large forms more convenient for developers. Add `FormControl` and `FormGroup` to the import from `@angular/forms` so that you can create a FormGroup for each form, with the properties `name` and `email` as FormControls. import {ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';...export class AppComponent { profileForm = new FormGroup({ name: new FormControl(''), email: new FormControl(''), });} 3. ### [Link the FormGroup and FormControls to the form](https://angular.dev/#link-the-formgroup-and-formcontrols-to-the-form) Each `FormGroup` should be attached to a form using the `[formGroup]` directive. In addition, each `FormControl` can be attached with the `formControlName` directive and assigned to the corresponding property. Update the template with the following form code: <form [formGroup]="profileForm"> <label> Name <input type="text" formControlName="name" /> </label> <label> Email <input type="email" formControlName="email" /> </label> <button type="submit">Submit</button></form> 4. ### [Handle update to the form](https://angular.dev/#handle-update-to-the-form) When you want to access data from the `FormGroup`, it can be done by accessing the value of the `FormGroup`. Update the `template` to display the form values: ...<h2>Profile Form</h2><p>Name: {{ profileForm.value.name }}</p><p>Email: {{ profileForm.value.email }}</p> 5. ### [Access FormGroup values](https://angular.dev/#access-formgroup-values) Add a new method to the component class called `handleSubmit` that you'll later use to handle the form submission. This method will display values from the form, you can access the values from the FormGroup. In the component class, add the `handleSubmit()` method to handle the form submission. handleSubmit() { alert( this.profileForm.value.name + ' | ' + this.profileForm.value.email );} 6. ### [Add `ngSubmit` to the form](https://angular.dev/#add-ngsubmit-to-the-form) You have access to the form values, now it is time to handle the submission event and use the `handleSubmit` method. Angular has an event handler for this specific purpose called `ngSubmit`. Update the form element to call the `handleSubmit` method when the form is submitted. <form [formGroup]="profileForm" (ngSubmit)="handleSubmit()"> And just like that, you know how to work with reactive forms in Angular. Fantastic job with this activity. Keep going to learn about form validation. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/forms/template-driven-forms ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/18-forms-validation Another common scenario when working with forms is the need to validate the inputs to ensure the correct data is submitted. Note: Learn more about [validating form input in the in-depth guide](https://angular.dev/guide/forms/reactive-forms#validating-form-input). In this activity, you'll learn how to validate forms with reactive forms. * * * 1. ### [Import Validators](https://angular.dev/#import-validators) Angular provides a set of validation tools. To use them, first update the component to import `Validators` from `@angular/forms`. import {ReactiveFormsModule, Validators} from '@angular/forms';@Component({...})export class AppComponent {} 2. ### [Add validation to form](https://angular.dev/#add-validation-to-form) Every `FormControl` can be passed the `Validators` you want to use for validating the `FormControl` values. For example, if you want to make the `name` field in `profileForm` required then use `Validators.required`. For the `email` field in our Angular form, we want to ensure it's not left empty and follows a valid email address structure. We can achieve this by combining the `Validators.required` and `Validators.email` validators in an array. Update the `name` and `email` `FormControl`: profileForm = new FormGroup({ name: new FormControl('', Validators.required), email: new FormControl('', [Validators.required, Validators.email]),}); 3. ### [Check form validation in template](https://angular.dev/#check-form-validation-in-template) To determine if a form is valid, the `FormGroup` class has a `valid` property. You can use this property to dynamically bind attributes. Update the submit `button` to be enabled based on the validity of the form. <button type="submit" [disabled]="!profileForm.valid">Submit</button> You now know the basics around how validation works with reactive forms. Great job learning these core concepts of working with forms in Angular. If you want to learn more, be sure to refer to the [Angular forms documentation](https://angular.dev/guide/forms/form-validation). --- ## Page: https://angular.dev/tutorials/learn-angular/19-creating-an-injectable-service Dependency injection (DI) in Angular is one of the framework's most powerful features. Consider dependency injection to be the ability for Angular to _provide_ resources you need for your application at runtime. A dependency could be a service or some other resources. Note: Learn more about [dependency injection in the essentials guide](https://angular.dev/essentials/dependency-injection). In this activity, you'll learn how to create an `injectable` service. * * * One way to use a service is to act as a way to interact with data and APIs. To make a service reusable you should keep the logic in the service and share it throughout the application when it is needed. To make a service eligible to be injected by the DI system use the `@Injectable` decorator. For example: @Injectable({ providedIn: 'root'})class UserService { // methods to retrieve and return data} The `@Injectable` decorator notifies the DI system that the `UserService` is available to be requested in a class. `providedIn` sets the scope in which this resource is available. For now, it is good enough to understand that `providedIn: 'root'` means that the `UserService` is available to the entire application. Alright, you try: 1. ### [Add the `@Injectable` decorator](https://angular.dev/#add-the-injectable-decorator) Update the code in `car.service.ts` by adding the `@Injectable` decorator. 2. ### [Configure the decorator](https://angular.dev/#configure-the-decorator) The values in the object passed to the decorator are considered to be the configuration for the decorator. Update the `@Injectable` decorator in `car.service.ts` to include the configuration for `providedIn: 'root'`. TIP: Use the above example to find the correct syntax. Well, done 👍 that service is now `injectable` and can participate in the fun. Now that the service is `injectable`, let's try injecting it into a component 👉 --- ## Page: https://angular.dev/tutorials/learn-angular/guide/forms/form-validation ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/20-inject-based-di Creating an injectable service is the first part of the dependency injection (DI) system in Angular. How do you inject a service into a component? Angular has a convenient function called `inject()` that can be used in the proper context. In this activity, you'll learn how to inject a service and use it in a component. * * * It is often helpful to initialize class properties with values provided by the DI system. Here's an example: @Component({...})class PetCareDashboardComponent { petRosterService = inject(PetRosterService);} 1. ### [Inject the `CarService`](https://angular.dev/#inject-the-carservice) In `app.component.ts`, using the `inject()` function inject the `CarService` and assign it to a property called `carService` NOTE: Notice the difference between the property `carService` and the class `CarService`. 2. ### [Use the `carService` instance](https://angular.dev/#use-the-carservice-instance) Calling `inject(CarService)` gave you an instance of the `CarService` that you can use in your application, stored in the `carService` property. In the `constructor` function of the `AppComponent`, add the following implementation: constructor() { this.display = this.carService.getCars().join(' ⭐️ ');} 3. ### [Update the `AppComponent` template](https://angular.dev/#update-the-appcomponent-template) Update the component template in `app.component.ts` with the following code: template: `<p>Car Listing: {{ display }}</p>`, You've just injected your first service into a component - fantastic effort. Before you finish this section on DI, you'll learn an alternative syntax to inject resources into your components. --- ## Page: https://angular.dev/tutorials/learn-angular/21-constructor-based-di In previous activities you used the `inject()` function to make resources available, "providing" them to your components. The `inject()` function is one pattern and it is useful to know that there is another pattern for injecting resources called constructor-based dependency injection. You specify the resources as parameters to the `constructor` function of a component. Angular will make those resources available to your component. Note: Learn more about [injecting services in the in-depth guide](https://angular.dev/guide/di/creating-injectable-service#injecting-services). In this activity, you will learn how to use constructor-based dependency injection. * * * To inject a service or some other injectable resource into your component use the following syntax: @Component({...})class PetCarDashboardComponent { constructor(private petCareService: PetCareService) { ... }} There are a few things to notice here: * Use the `private` keyword * The `petCareService` becomes a property you can use in your class * The `PetCareService` class is the injected class Alright, now you give this a try: 1. ### [Update the code to use constructor-based DI](https://angular.dev/#update-the-code-to-use-constructor-based-di) In `app.component.ts`, update the constructor code to match the code below: **TIP:** Remember, if you get stuck refer to the example on this activity page. constructor(private carService: CarService) { this.display = this.carService.getCars().join(' ⭐️ ');} Congratulations on completing this activity. The example code works the same as with using the `inject` function. While these two approaches are largely the same, there are some small differences that are beyond the scope of this tutorial. You can find out more information about dependency injection in the [Angular Documentation](https://angular.dev/guide/di). --- ## Page: https://angular.dev/tutorials/learn-angular/guide/di/dependency-injection-context ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/22-pipes Pipes are functions that are used to transform data in templates. In general, pipes are "pure" functions that don't cause side effects. Angular has a number of helpful built-in pipes you can import and use in your components. You can also create a custom pipe. Note: Learn more about [pipes in the in-depth guide](https://angular.dev/guide/templates/pipes). In this activity, you will import a pipe and use it in the template. * * * To use a pipe in a template include it in an interpolated expression. Check out this example: import {UpperCasePipe} from '@angular/common';@Component({...template: `{{ loudMessage | uppercase }}`,imports: [UpperCasePipe],})class AppComponent {loudMessage = 'we think you are doing great!'} Now, it's your turn to give this a try: 1. ### [Import the `LowerCase` pipe](https://angular.dev/#import-the-lowercase-pipe) First, update `app.component.ts` by adding the file level import for `LowerCasePipe` from `@angular/common`. import { LowerCasePipe } from '@angular/common'; 2. ### [Add the pipe to the template imports](https://angular.dev/#add-the-pipe-to-the-template-imports) Next, update `@Component()` decorator `imports` to include a reference to `LowerCasePipe` @Component({ ... imports: [LowerCasePipe]}) 3. ### [Add the pipe to the template](https://angular.dev/#add-the-pipe-to-the-template) Finally, in `app.component.ts` update the template to include the `lowercase` pipe: template: `{{username | lowercase }}` Pipes can also accept parameters which can be used to configure their output. Find out more in the next activity. P.S. you are doing great ⭐️ --- ## Page: https://angular.dev/tutorials/learn-angular/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/23-pipes-format-data You can take your use of pipes even further by configuring them. Pipes can be configured by passing options to them. Note: Learn more about [formatting data with pipes in the in-depth guide](https://angular.dev/guide/templates/pipes). In this activity, you will work with some pipes and pipe parameters. * * * To pass parameters to a pipe, use the `:` syntax followed by the parameter value. Here's an example: template: `{{ date | date:'medium' }}`; The output is `Jun 15, 2015, 9:43:11 PM`. Time to customize some pipe output: 1. ### [Format a number with `DecimalPipe`](https://angular.dev/#format-a-number-with-decimalpipe) In `app.component.ts`, update the template to include parameter for the `decimal` pipe. template: ` ... <li>Number with "decimal" {{ num | number:"3.2-2" }}</li>` NOTE: What's that format? The parameter for the `DecimalPipe` is called `digitsInfo`, this parameter uses the format: `{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}` 2. ### [Format a date with `DatePipe`](https://angular.dev/#format-a-date-with-datepipe) Now, update the template to use the `date` pipe. template: ` ... <li>Date with "date" {{ birthday | date: 'medium' }}</li>` For extra fun, try some different parameters for `date`. More information can be found in the [Angular docs](https://angular.dev/guide/templates/pipes). 3. ### [Format a currency with `CurrencyPipe`](https://angular.dev/#format-a-currency-with-currencypipe) For your last task, update the template to use the `currency` pipe. template: ` ... <li>Currency with "currency" {{ cost | currency }}</li>` You can also try different parameters for `currency`. More information can be found in the [Angular docs](https://angular.dev/guide/templates/pipes). Great work with pipes. You've made some great progress so far. There are even more built-in pipes that you can use in your applications. You can find the list in the [Angular documentation](https://angular.dev/guide/templates/pipes). In the case that the built-in pipes don't cover your needs, you can also create a custom pipe. Check out the next lesson to find out more. --- ## Page: https://angular.dev/tutorials/learn-angular/24-create-a-pipe You can create custom pipes in Angular to fit your data transformation needs. Note: Learn more about [creating custom pipes in the in-depth guide](https://angular.dev/guide/templates/pipes#creating-custom-pipes). In this activity, you will create a custom pipe and use it in your template. * * * A pipe is a TypeScript class with a `@Pipe` decorator. Here's an example: import {Pipe, PipeTransform} from '@angular/core';@Pipe({ name: 'star',})export class StarPipe implements PipeTransform { transform(value: string): string { return `⭐️ ${value} ⭐️`; }} The `StarPipe` accepts a string value and returns that string with stars around it. Take note that: * the name in the `@Pipe` decorator configuration is what will be used in the template * the `transform` function is where you put your logic Alright, it's your turn to give this a try — you'll create the `ReversePipe`: 1. ### [Create the `ReversePipe`](https://angular.dev/#create-the-reversepipe) In `reverse.pipe.ts` add the `@Pipe` decorator to the `ReversePipe` class and provide the following configuration: @Pipe({ name: 'reverse'}) 2. ### [Implement the `transform` function](https://angular.dev/#implement-the-transform-function) Now the `ReversePipe` class is a pipe. Update the `transform` function to add the reversing logic: export class ReversePipe implements PipeTransform { transform(value: string): string { let reverse = ''; for (let i = value.length - 1; i >= 0; i--) { reverse += value[i]; } return reverse; }} 3. ### [Use the `ReversePipe` in the template](https://angular.dev/#use-the-reversepipe-in-the-template) With the pipe logic implemented, the final step is to use it in the template. In `app.component.ts` include the pipe in the template and add it to the component imports: @Component({ ... template: `Reverse Machine: {{ word | reverse }}` imports: [ReversePipe]}) And with that you've done it. Congratulations on completing this activity. You now know how to use pipes and even how to implement your own custom pipes. --- ## Page: https://angular.dev/tutorials/learn-angular/guide/templates/pipes ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/25-next-steps ## Ready to explore more of Angular? [](https://github.com/angular/angular/edit/main/adev/src/content/tutorials/learn-angular/steps/25-next-steps/README.md "Edit this page") You can also learn more in our [guides](https://angular.dev/overview) and [reference](https://angular.dev/api), or `ng new`. [ ### What is Angular? Angular is the web development framework for building modern apps! Learn more](https://angular.dev/overview)[ ### Angular's Roadmap Read about Angular's open source roadmap including current, future and accomplished projects. Learn more](https://angular.dev/roadmap)[ ### Playground Play with Angular in your browser! Start playing](https://angular.dev/playground)[ ### Angular YouTube Course Check out the official Angular YouTube channel for videos, courses and more! Start learning](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF) --- ## Page: https://angular.dev/tutorials/learn-angular/overview ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/api ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/roadmap ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tutorials/learn-angular/playground ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/signals **TIP:** Check out Angular's [Essentials](https://angular.dev/essentials/signals) before diving into this comprehensive guide. ## [What are signals?](https://angular.dev/#what-are-signals) A **signal** is a wrapper around a value that notifies interested consumers when that value changes. Signals can contain any value, from primitives to complex data structures. You read a signal's value by calling its getter function, which allows Angular to track where the signal is used. Signals may be either _writable_ or _read-only_. ### [Writable signals](https://angular.dev/#writable-signals) Writable signals provide an API for updating their values directly. You create writable signals by calling the `signal` function with the signal's initial value: const count = signal(0);// Signals are getter functions - calling them reads their value.console.log('The count is: ' + count()); To change the value of a writable signal, either `.set()` it directly: count.set(3); or use the `.update()` operation to compute a new value from the previous one: // Increment the count by 1.count.update(value => value + 1); Writable signals have the type `WritableSignal`. ### [Computed signals](https://angular.dev/#computed-signals) **Computed signal** are read-only signals that derive their value from other signals. You define computed signals using the `computed` function and specifying a derivation: const count: WritableSignal<number> = signal(0);const doubleCount: Signal<number> = computed(() => count() * 2); The `doubleCount` signal depends on the `count` signal. Whenever `count` updates, Angular knows that `doubleCount` needs to update as well. #### [Computed signals are both lazily evaluated and memoized](https://angular.dev/#computed-signals-are-both-lazily-evaluated-and-memoized) `doubleCount`'s derivation function does not run to calculate its value until the first time you read `doubleCount`. The calculated value is then cached, and if you read `doubleCount` again, it will return the cached value without recalculating. If you then change `count`, Angular knows that `doubleCount`'s cached value is no longer valid, and the next time you read `doubleCount` its new value will be calculated. As a result, you can safely perform computationally expensive derivations in computed signals, such as filtering arrays. #### [Computed signals are not writable signals](https://angular.dev/#computed-signals-are-not-writable-signals) You cannot directly assign values to a computed signal. That is, doubleCount.set(3); produces a compilation error, because `doubleCount` is not a `WritableSignal`. #### [Computed signal dependencies are dynamic](https://angular.dev/#computed-signal-dependencies-are-dynamic) Only the signals actually read during the derivation are tracked. For example, in this `computed` the `count` signal is only read if the `showCount` signal is true: const showCount = signal(false);const count = signal(0);const conditionalCount = computed(() => { if (showCount()) { return `The count is ${count()}.`; } else { return 'Nothing to see here!'; }}); When you read `conditionalCount`, if `showCount` is `false` the "Nothing to see here!" message is returned _without_ reading the `count` signal. This means that if you later update `count` it will _not_ result in a recomputation of `conditionalCount`. If you set `showCount` to `true` and then read `conditionalCount` again, the derivation will re-execute and take the branch where `showCount` is `true`, returning the message which shows the value of `count`. Changing `count` will then invalidate `conditionalCount`'s cached value. Note that dependencies can be removed during a derivation as well as added. If you later set `showCount` back to `false`, then `count` will no longer be considered a dependency of `conditionalCount`. ## [Reading signals in `OnPush` components](https://angular.dev/#reading-signals-in-onpush-components) When you read a signal within an `OnPush` component's template, Angular tracks the signal as a dependency of that component. When the value of that signal changes, Angular automatically [marks](https://angular.dev/api/core/ChangeDetectorRef#markforcheck) the component to ensure it gets updated the next time change detection runs. Refer to the [Skipping component subtrees](https://angular.dev/best-practices/skipping-subtrees) guide for more information about `OnPush` components. ## [Effects](https://angular.dev/#effects) Signals are useful because they notify interested consumers when they change. An **effect** is an operation that runs whenever one or more signal values change. You can create an effect with the `effect` function: effect(() => { console.log(`The current count is: ${count()}`);}); Effects always run **at least once.** When an effect runs, it tracks any signal value reads. Whenever any of these signal values change, the effect runs again. Similar to computed signals, effects keep track of their dependencies dynamically, and only track signals which were read in the most recent execution. Effects always execute **asynchronously**, during the change detection process. ### [Use cases for effects](https://angular.dev/#use-cases-for-effects) Effects are rarely needed in most application code, but may be useful in specific circumstances. Here are some examples of situations where an `effect` might be a good solution: * Logging data being displayed and when it changes, either for analytics or as a debugging tool. * Keeping data in sync with `window.localStorage`. * Adding custom DOM behavior that can't be expressed with template syntax. * Performing custom rendering to a `<canvas>`, charting library, or other third party UI library. ### When not to use effects Avoid using effects for propagation of state changes. This can result in `ExpressionChangedAfterItHasBeenChecked` errors, infinite circular updates, or unnecessary change detection cycles. Instead, use `computed` signals to model state that depends on other state. ### [Injection context](https://angular.dev/#injection-context) By default, you can only create an `effect()` within an [injection context](https://angular.dev/guide/di/dependency-injection-context) (where you have access to the `inject` function). The easiest way to satisfy this requirement is to call `effect` within a component, directive, or service `constructor`: @Component({...})export class EffectiveCounterComponent { readonly count = signal(0); constructor() { // Register a new effect. effect(() => { console.log(`The count is: ${this.count()}`); }); }} Alternatively, you can assign the effect to a field (which also gives it a descriptive name). @Component({...})export class EffectiveCounterComponent { readonly count = signal(0); private loggingEffect = effect(() => { console.log(`The count is: ${this.count()}`); });} To create an effect outside the constructor, you can pass an `Injector` to `effect` via its options: @Component({...})export class EffectiveCounterComponent { readonly count = signal(0); private injector = inject(Injector); initializeLogging(): void { effect(() => { console.log(`The count is: ${this.count()}`); }, {injector: this.injector}); }} ### [Destroying effects](https://angular.dev/#destroying-effects) When you create an effect, it is automatically destroyed when its enclosing context is destroyed. This means that effects created within components are destroyed when the component is destroyed. The same goes for effects within directives, services, etc. Effects return an `EffectRef` that you can use to destroy them manually, by calling the `.destroy()` method. You can combine this with the `manualCleanup` option to create an effect that lasts until it is manually destroyed. Be careful to actually clean up such effects when they're no longer required. ## [Advanced topics](https://angular.dev/#advanced-topics) ### [Signal equality functions](https://angular.dev/#signal-equality-functions) When creating a signal, you can optionally provide an equality function, which will be used to check whether the new value is actually different than the previous one. import _ from 'lodash';const data = signal(['test'], {equal: _.isEqual});// Even though this is a different array instance, the deep equality// function will consider the values to be equal, and the signal won't// trigger any updates.data.set(['test']); Equality functions can be provided to both writable and computed signals. **HELPFUL:** By default, signals use referential equality ([`Object.is()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison). ### [Reading without tracking dependencies](https://angular.dev/#reading-without-tracking-dependencies) Rarely, you may want to execute code which may read signals within a reactive function such as `computed` or `effect` _without_ creating a dependency. For example, suppose that when `currentUser` changes, the value of a `counter` should be logged. you could create an `effect` which reads both signals: effect(() => { console.log(`User set to ${currentUser()} and the counter is ${counter()}`);}); This example will log a message when _either_ `currentUser` or `counter` changes. However, if the effect should only run when `currentUser` changes, then the read of `counter` is only incidental and changes to `counter` shouldn't log a new message. You can prevent a signal read from being tracked by calling its getter with `untracked`: effect(() => { console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);}); `untracked` is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency: effect(() => { const user = currentUser(); untracked(() => { // If the `loggingService` reads signals, they won't be counted as // dependencies of this effect. this.loggingService.log(`User set to ${user}`); });}); ### [Effect cleanup functions](https://angular.dev/#effect-cleanup-functions) Effects might start long-running operations, which you should cancel if the effect is destroyed or runs again before the first operation finished. When you create an effect, your function can optionally accept an `onCleanup` function as its first parameter. This `onCleanup` function lets you register a callback that is invoked before the next run of the effect begins, or when the effect is destroyed. effect((onCleanup) => { const user = currentUser(); const timer = setTimeout(() => { console.log(`1 second ago, the user became ${user}`); }, 1000); onCleanup(() => { clearTimeout(timer); });}); ## [Using signals with RxJS](https://angular.dev/#using-signals-with-rxjs) See [RxJS interop with Angular signals](https://angular.dev/ecosystem/rxjs-interop) for details on interoperability between signals and RxJS. --- ## Page: https://angular.dev/guide/signals/linked-signal **IMPORTANT:** `linkedSignal` is [developer preview](https://angular.dev/reference/releases#developer-preview). It's ready for you to try, but it might change before it is stable. 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](https://angular.dev/#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 result of the `computation`. 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](https://angular.dev/#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,}); --- ## Page: https://angular.dev/guide/signals/resource **IMPORTANT:** `resource` is [experimental](https://angular.dev/reference/releases#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 request computation. // The request value recomputes whenever any read signals change. request: () => ({id: userId()}), // Define an async loader that retrieves data. // The resource calls this function every time the `request` value changes. loader: ({request}) => fetchUser(request),});// Create a computed signal based on the result of the resource's loader function.const firstName = computed(() => userResource.value().firstName); The `resource` function accepts a `ResourceOptions` object with two main properties: `request` and `loader`. The `request` property defines a reactive computation that produce a request value. Whenever signals read in this computation change, the resource produces a new request 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 `request` computation produces a new value, passing that value to the loader. See [Resource loaders](https://angular.dev/#resource-loaders) below for more details. `Resource` has a `value` signal that contains the results of the loader. ## [Resource loaders](https://angular.dev/#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: `request`, `previous`, and `abortSignal`. | Property | Description | | --- | --- | | `request` | The value of the resource's `request` computation. | | `previous` | An object with a `status` property, containing the previous `ResourceStatus`. | | `abortSignal` | An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). See [Aborting requests](https://angular.dev/#aborting-requests) below for details. | If the `request` computation returns `undefined`, the loader function does not run and the resource status becomes `Idle`. ### [Aborting requests](https://angular.dev/#aborting-requests) A resource aborts an outstanding request if the `request` 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({ request: () => ({id: userId()}), loader: ({request, abortSignal}): Promise<User> => { // fetch cancels any outstanding HTTP requests when the given `AbortSignal` // indicates that the request has been aborted. return fetch(`users/${request.id}`, {signal: abortSignal}); },}); See [`AbortSignal` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) for more details on request cancellation with `AbortSignal`. ### [Reloading](https://angular.dev/#reloading) You can programmatically trigger a resource's `loader` by calling the `reload` method. const userId: Signal<string> = getUserId();const userResource = resource({ request: () => ({id: userId()}), loader: ({request}) => fetchUser(request),});// ...userResource.reload(); ## [Resource status](https://angular.dev/#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. | 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 `request` 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. --- ## Page: https://angular.dev/guide/signals/reference/releases#developer-preview ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. Every component must have: * A TypeScript class with _behaviors_ such as handling user input and fetching data from a server * An HTML template that controls what renders into the DOM * A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML You provide Angular-specific information for a component by adding a `@Component` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class: @Component({ selector: 'profile-photo', template: `<img src="profile-photo.jpg" alt="Your profile photo">`,})export class ProfilePhoto { } For full details on writing Angular templates, including data binding, event handling, and control flow, see the [Templates guide](https://angular.dev/guide/templates). The object passed to the `@Component` decorator is called the component's **metadata**. This includes the `selector`, `template`, and other properties described throughout this guide. Components can optionally include a list of CSS styles that apply to that component's DOM: @Component({ selector: 'profile-photo', template: `<img src="profile-photo.jpg" alt="Your profile photo">`, styles: `img { border-radius: 50%; }`,})export class ProfilePhoto { } By default, a component's styles only affect elements defined in that component's template. See [Styling Components](https://angular.dev/guide/components/styling) for details on Angular's approach to styling. You can alternatively choose to write your template and styles in separate files: @Component({ selector: 'profile-photo', templateUrl: 'profile-photo.html', styleUrl: 'profile-photo.css',})export class ProfilePhoto { } This can help separate the concerns of _presentation_ from _behavior_ in your project. You can choose one approach for your entire project, or you decide which to use for each component. Both `templateUrl` and `styleUrl` are relative to the directory in which the component resides. ## [Using components](https://angular.dev/#using-components) ### [Imports in the `@Component` decorator](https://angular.dev/#imports-in-the-component-decorator) To use a component, [directive](https://angular.dev/guide/directives), or [pipe](https://angular.dev/guide/templates/pipes), you must add it to the `imports` array in the `@Component` decorator: import {ProfilePhoto} from './profile-photo';@Component({ // Import the `ProfilePhoto` component in // order to use it in this component's template. imports: [ProfilePhoto], /* ... */})export class UserProfile { } By default, Angular components are _standalone_, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their `@Component` decorator. For these components, you instead import the `NgModule` in which the component is defined. See the full [`NgModule` guide](https://angular.dev/guide/ngmodules) for details. **IMPORTANT:** In Angular versions before 19.0.0, the `standalone` option defaults to `false`. ### [Showing components in a template](https://angular.dev/#showing-components-in-a-template) Every component defines a [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors): @Component({ selector: 'profile-photo', ...})export class ProfilePhoto { } See [Component Selectors](https://angular.dev/guide/components/selectors) for details about which types of selectors Angular supports and guidance on choosing a selector. You show a component by creating a matching HTML element in the template of _other_ components: @Component({ selector: 'profile-photo',})export class ProfilePhoto { }@Component({ imports: [ProfilePhoto], template: `<profile-photo />`})export class UserProfile { } Angular creates an instance of the component for every matching HTML element it encounters. The DOM element that matches a component's selector is referred to as that component's **host element**. The contents of a component's template are rendered inside its host element. The DOM rendered by a component, corresponding to that component's template, is called that component's **view**. In composing components in this way, **you can think of your Angular application as a tree of components**. AccountSettings UserProfile PaymentInfo ProfilePic UserBio This tree structure is important to understanding several other Angular concepts, including [dependency injection](https://angular.dev/guide/di) and [child queries](https://angular.dev/guide/components/queries). --- ## Page: https://angular.dev/guide/components/selectors **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. Every component defines a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_selectors) that determines how the component is used: @Component({ selector: 'profile-photo', ...})export class ProfilePhoto { } You use a component by creating a matching HTML element in the templates of _other_ components: @Component({ template: ` <profile-photo /> <button>Upload a new profile photo</button>`, ...,})export class UserProfile { } **Angular matches selectors statically at compile-time**. Changing the DOM at run-time, either via Angular bindings or with DOM APIs, does not affect the components rendered. **An element can match exactly one component selector.** If multiple component selectors match a single element, Angular reports an error. **Component selectors are case-sensitive.** ## [Types of selectors](https://angular.dev/#types-of-selectors) Angular supports a limited subset of [basic CSS selector types](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) in component selectors: | **Selector type** | **Description** | **Examples** | | --- | --- | --- | | Type selector | Matches elements based on their HTML tag name, or node name. | `profile-photo` | | Attribute selector | Matches elements based on the presence of an HTML attribute and, optionally, an exact value for that attribute. | `[dropzone]` `[type="reset"]` | | Class selector | Matches elements based on the presence of a CSS class. | `.menu-item` | For attribute values, Angular supports matching an exact attribute value with the equals (`=`) operator. Angular does not support other attribute value operators. Angular component selectors do not support combinators, including the [descendant combinator](https://developer.mozilla.org/docs/Web/CSS/Descendant_combinator) or [child combinator](https://developer.mozilla.org/docs/Web/CSS/Child_combinator). Angular component selectors do not support specifying [namespaces](https://developer.mozilla.org/docs/Web/SVG/Namespaces_Crash_Course). ### [The `:not` pseudo-class](https://angular.dev/#the-not-pseudo-class) Angular supports [the `:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not). You can append this pseudo-class to any other selector to narrow which elements a component's selector matches. For example, you could define a `[dropzone]` attribute selector and prevent matching `textarea` elements: @Component({ selector: '[dropzone]:not(textarea)', ...})export class DropZone { } Angular does not support any other pseudo-classes or pseudo-elements in component selectors. ### [Combining selectors](https://angular.dev/#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](https://angular.dev/#choosing-a-selector) The vast majority of components should use a custom element name as their selector. All custom element names should include a hyphen as described by [the HTML specification](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name). By default, Angular reports an error if it encounters a custom tag name that does not match any available components, preventing bugs due to mistyped component names. See [Advanced component configuration](https://angular.dev/guide/components/advanced-configuration) for details on using [native custom elements](https://developer.mozilla.org/docs/Web/Web_Components) in Angular templates. ### [Selector prefixes](https://angular.dev/#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-`. 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](https://angular.dev/#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](https://angular.dev/guide/components/importing) for more information. Components that define attribute selectors should use lowercase, dash-case attributes. You can follow the same prefixing recommendation described above. --- ## Page: https://angular.dev/guide/components/styling **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). 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](https://sass-lang.com/), [less](https://lesscss.org/), and [stylus](https://stylus-lang.com/). ## [Style scoping](https://angular.dev/#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](https://angular.dev/#viewencapsulationemulated) By default, Angular uses emulated encapsulation so that a component's styles only apply to elements defined in that component's template. In this mode, the framework generates a unique HTML attribute for each component instance, adds that attribute to elements in the component's template, and inserts that attribute into the CSS selectors defined in your component's styles. This mode ensures that a component's styles do not leak out and affect other components. However, global styles defined outside of a component may still affect elements inside a component with emulated encapsulation. In emulated mode, Angular supports the [`:host`](https://developer.mozilla.org/docs/Web/CSS/:host) and [`:host-context()`](https://developer.mozilla.org/docs/Web/CSS/:host-context) pseudo classes without using [Shadow DOM](https://developer.mozilla.org/docs/Web/Web_Components/Using_shadow_DOM). During compilation, the framework transforms these pseudo classes into attributes so it doesn't comply with these native pseudo classes' rules at runtime (e.g. browser compatibility, specificity). Angular's emulated encapsulation mode does not support any other pseudo classes related to Shadow DOM, such as `::shadow` or `::part`. #### [`::ng-deep`](https://angular.dev/#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](https://angular.dev/#viewencapsulationshadowdom) This mode scopes styles within a component by using [the web standard Shadow DOM API](https://developer.mozilla.org/docs/Web/Web_Components/Using_shadow_DOM). When enabling this mode, Angular attaches a shadow root to the component's host element and renders the component's template and styles into the corresponding shadow tree. 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](https://developer.mozilla.org/docs/Web/Web_Components/Using_templates_and_slots), and how browser developer tools show elements. Always understand the full implications of using Shadow DOM in your application before enabling this option. ### [ViewEncapsulation.None](https://angular.dev/#viewencapsulationnone) This mode disables all style encapsulation for the component. Any styles associated with the component behave as global styles. **NOTE:** In `Emulated` and `ShadowDom` modes, Angular doesn't 100% guarantee that your component's styles will always override styles coming from outside it. It is assumed that these styles have the same specificity as your component's styles in case of collision. ## [Defining styles in templates](https://angular.dev/#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](https://angular.dev/#referencing-external-style-files) Component templates can use [the `<link>` element](https://developer.mozilla.org/docs/Web/HTML/Element/link) to reference CSS files. Additionally, your CSS may use [the `@import`at-rule](https://developer.mozilla.org/docs/Web/CSS/@import) to reference CSS files. Angular treats these references as _external_ styles. External styles are not affected by emulated view encapsulation. --- ## Page: https://angular.dev/guide/components/inputs **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#configuring-inputs) The `input` function accepts a config object as a second parameter that lets you change the way that input works. ### [Input transforms](https://angular.dev/#input-transforms) You can specify a `transform` function to change the value of an input when it's set by Angular. @Component({ selector: 'custom-slider', /*...*/})export class CustomSlider { label = input('', {transform: trimString});}function trimString(value: string | undefined): string { return value?.trim() ?? '';} <custom-slider [label]="systemVolume" /> In the example above, whenever the value of `systemVolume` changes, Angular runs `trimString` and sets `label` to the result. The most common use-case for input transforms is to accept a wider range of value types in templates, often including `null` and `undefined`. **Input transform function must be statically analyzable at build-time.** You cannot set transform functions conditionally or as the result of an expression evaluation. **Input transform functions should always be [pure functions](https://en.wikipedia.org/wiki/Pure_function).** Relying on state outside the transform function can lead to unpredictable behavior. #### [Type checking](https://angular.dev/#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](https://angular.dev/#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](https://developer.mozilla.org/docs/Glossary/Boolean/HTML), 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/templates/two-way-binding) for more details on two-way binding in templates. ### [Two-way binding with plain properties](https://angular.dev/#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](https://angular.dev/#implicit-change-events) When you declare a model input in a component or directive, Angular automatically creates a corresponding [output](https://angular.dev/guide/components/outputs) for that model. The output's name is the model input's name suffixed with "Change". @Directive({ /* ... */ })export class CustomCheckbox { // This automatically creates an output named "checkedChange". // Can be subscribed to using `(checkedChange)="handler()"` in the template. checked = model(false);} Angular emits this change event whenever you write a new value into the model input by calling its `set` or `update` methods. See [Custom events with outputs](https://angular.dev/guide/components/outputs) for more details on outputs. ### [Customizing model inputs](https://angular.dev/#customizing-model-inputs) You can mark a model input as required or provide an alias in the same way as a [standard input](https://angular.dev/guide/signals/inputs). Model inputs do not support input transforms. ### [When to use model inputs](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#customizing-decorator-based-inputs) The `@Input` decorator accepts a config object that lets you change the way that input works. #### [Required inputs](https://angular.dev/#required-inputs-1) 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](https://angular.dev/#input-transforms-1) 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](https://angular.dev/#input-aliases-1) 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](https://angular.dev/#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](https://angular.dev/#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 { } --- ## Page: https://angular.dev/guide/components/outputs **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). 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](https://angular.dev/#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 objectsthis.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)" /> ## [Customizing output names](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#choosing-event-names) Avoid choosing output names that collide with events on DOM elements like HTMLElement. Name collisions introduce confusion about whether the bound property belongs to the component or the DOM element. Avoid adding prefixes for component outputs like you would with component selectors. Since a given element can only host one component, any custom properties can be assumed to belong to the component. Always use [camelCase](https://en.wikipedia.org/wiki/Camel_case) output names. Avoid prefixing output names with "on". ## [Using outputs with RxJS](https://angular.dev/#using-outputs-with-rxjs) See [RxJS interop with component and directive outputs](https://angular.dev/ecosystem/rxjs-interop/output-interop) for details on interoperability between outputs and RxJS. ## [Declaring outputs with the `@Output` decorator](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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 {} --- ## Page: https://angular.dev/guide/components/content-projection **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). 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](https://developer.mozilla.org/docs/Web/HTML/Element/slot), but with some Angular-specific functionality. When you use a component with `<ng-content>`, any children of the component host element are rendered, or **projected**, at the location of that `<ng-content>`: // Component source@Component({ selector: 'custom-card', template: ` <div class="card-shadow"> <ng-content /> </div> `,})export class CustomCard {/* ... */} <!-- Using the component --><custom-card> <p>This is the projected content</p></custom-card> <!-- The rendered DOM --><custom-card> <div class="card-shadow"> <p>This is the projected content</p> </div></custom-card> Angular refers to any children of a component passed this way as that component's **content**. This is distinct from the component's **view**, which refers to the elements defined in the component's template. **The `<ng-content>` element is neither a component nor DOM element**. Instead, it is a special placeholder that tells Angular where to render content. Angular's compiler processes all `<ng-content>` elements at build-time. You cannot insert, remove, or modify `<ng-content>` at run time. You cannot add directives, styles, or arbitrary attributes to `<ng-content>`. 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](https://angular.dev/api/core/ng-template). ## [Multiple content placeholders](https://angular.dev/#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 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> <!-- Using the component --><custom-card> <card-title>Hello</card-title> <card-body>Welcome to the example</card-body></custom-card> <!-- 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](https://angular.dev/guide/components/selectors). If you include one or more `<ng-content>` placeholders with a `select` attribute and one `<ng-content>` placeholder without a `select` attribute, the latter captures all elements that did not match a `select` attribute: <!-- Component template --><div class="card-shadow"> <ng-content select="card-title"></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](https://angular.dev/#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](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/components/host-elements **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. Angular creates an instance of a component for every HTML element that matches the component's selector. The DOM element that matches a component's selector is that component's **host element**. The contents of a component's template are rendered inside its host element. // Component source@Component({ selector: 'profile-photo', template: ` <img src="profile-photo.jpg" alt="Your profile photo" /> `,})export class ProfilePhoto {} <!-- Using the component --><h3>Your profile photo</h3><profile-photo /><button>Upload a new profile photo</button> <!-- Rendered DOM --><h3>Your profile photo</h3><profile-photo> <img src="profile-photo.jpg" alt="Your profile photo" /></profile-photo><button>Upload a new profile photo</button> In the above example, `<profile-photo>` is the host element of the `ProfilePhoto` component. ## [Binding to the host element](https://angular.dev/#binding-to-the-host-element) A component can bind properties, attributes, and events to its host element. This behaves identically to bindings on elements inside the component's template, but instead defined with the `host` property in the `@Component` decorator: @Component({ ..., host: { 'role': 'slider', '[attr.aria-valuenow]': 'value', '[class.active]': 'isActive()', '[tabIndex]': 'disabled ? -1 : 0', '(keydown)': 'updateValue($event)', },})export class CustomSlider { value: number = 0; disabled: boolean = false; isActive = signal(false); updateValue(event: KeyboardEvent) { /* ... */ } /* ... */} ## [The `@HostBinding` and `@HostListener` decorators](https://angular.dev/#the-hostbinding-and-hostlistener-decorators) You can alternatively bind to the host element by applying the `@HostBinding` and `@HostListener` decorator to class members. `@HostBinding` lets you bind host properties and attributes to properties and methods: @Component({ /* ... */})export class CustomSlider { @HostBinding('attr.aria-valuenow') value: number = 0; @HostBinding('tabIndex') getTabIndex() { return this.disabled ? -1 : 0; } /* ... */} `@HostListener` lets you bind event listeners to the host element. The decorator accepts an event name and an optional array of arguments: export class CustomSlider { @HostListener('keydown', ['$event']) updateValue(event: KeyboardEvent) { /* ... */ }} **Always prefer using the `host` property over `@HostBinding` and `@HostListener`.** These decorators exist exclusively for backwards compatibility. ## [Binding collisions](https://angular.dev/#binding-collisions) When you use a component in a template, you can add bindings to that component instance's element. The component may _also_ define host bindings for the same properties or attributes. @Component({ ..., host: { 'role': 'presentation', '[id]': 'id', }})export class ProfilePhoto { /* ... */ } <profile-photo role="group" [id]="otherId" /> In cases like this, the following rules determine which value wins: * If both values are static, the instance binding wins. * If one value is static and the other dynamic, the dynamic value wins. * If both values are dynamic, the component's host binding wins. --- ## Page: https://angular.dev/guide/components/lifecycle **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). 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](https://angular.dev/#summary) <table><tbody><tr><td><strong>Phase</strong></td><td><strong>Method</strong></td><td><strong>Summary</strong></td></tr><tr><td>Creation</td><td><code>constructor</code></td><td><a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes/constructor" target="_blank">Standard JavaScript class constructor </a>. Runs when Angular instantiates the component.</td></tr><tr><td rowspan="7">Change<p>Detection</p></td><td><code>ngOnInit</code></td><td>Runs once after Angular has initialized all the component's inputs.</td></tr><tr><td><code>ngOnChanges</code></td><td>Runs every time the component's inputs have changed.</td></tr><tr><td><code>ngDoCheck</code></td><td>Runs every time this component is checked for changes.</td></tr><tr><td><code>ngAfterContentInit</code></td><td>Runs once after the component's <em>content</em> has been initialized.</td></tr><tr><td><code>ngAfterContentChecked</code></td><td>Runs every time this component content has been checked for changes.</td></tr><tr><td><code>ngAfterViewInit</code></td><td>Runs once after the component's <em>view</em> has been initialized.</td></tr><tr><td><code>ngAfterViewChecked</code></td><td>Runs every time the component's view has been checked for changes.</td></tr><tr><td rowspan="2">Rendering</td><td><code>afterNextRender</code></td><td>Runs once the next time that <strong>all</strong> components have been rendered to the DOM.</td></tr><tr><td><code>afterRender</code></td><td>Runs every time <strong>all</strong> components have been rendered to the DOM.</td></tr><tr><td>Destruction</td><td><code>ngOnDestroy</code></td><td>Runs once before the component is destroyed.</td></tr></tbody></table> ### [ngOnInit](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#inspecting-changes) The `ngOnChanges` method accepts one `SimpleChanges` argument. This object is a [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) mapping each component input name to a `SimpleChange` 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 { @Input() name: string = ''; 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/components/queries#content-queries). While you can access the initialized state of these queries, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](https://angular.dev/errors/NG0100) ### [ngAfterContentChecked](https://angular.dev/#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](https://angular.dev/guide/components/queries#content-queries) here, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](https://angular.dev/errors/NG0100). ### [ngAfterViewInit](https://angular.dev/#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](https://angular.dev/guide/components/queries#view-queries). While you can access the initialized state of these queries, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](https://angular.dev/errors/NG0100) ### [ngAfterViewChecked](https://angular.dev/#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](https://angular.dev/guide/components/queries#view-queries) here, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](https://angular.dev/errors/NG0100). ### [afterRender and afterNextRender](https://angular.dev/#afterrender-and-afternextrender) The `afterRender` 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. `afterRender` and `afterNextRender` must be called in an [injection context](https://angular.dev/guide/di/dependency-injection-context), typically a component's constructor. You can use render callbacks to perform manual DOM operations. See [Using DOM APIs](https://angular.dev/guide/components/dom-apis) for guidance on working with the DOM in Angular. Render callbacks do not run during server-side rendering or during build-time pre-rendering. #### [afterRender phases](https://angular.dev/#afterrender-phases) When using `afterRender` 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](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). In order to communicate across phases, a phase function may return a result value that can be accessed in the next phase. import {Component, ElementRef, afterNextRender} from '@angular/core';@Component({...})export class UserProfile { private prevPadding = 0; private elementHeight = 0; constructor() { 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](https://angular.dev/#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](https://angular.dev/#execution-order) The following diagrams show the execution order of Angular's lifecycle hooks. ### [During initialization](https://angular.dev/#during-initialization) Rendering Change detection ngOnChanges ngOnInit ngDoCheck ngAfterContentInit ngAfterViewInit ngAfterContentChecked ngAfterViewChecked constructor afterRender ### [Subsequent updates](https://angular.dev/#subsequent-updates) Rendering Change detection ngOnChanges ngDoCheck ngAfterContentChecked ngAfterViewChecked afterRender ### [Ordering with directives](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/components/queries **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. A component can define **queries** that find child elements and read values from their injectors. Developers most commonly use queries to retrieve references to child components, directives, DOM elements, and more. All query functions return signals that reflect the most up-to-date results. You can read the result by calling the signal function, including in reactive contexts like `computed` and `effect`. There are two categories of query: **view queries** and **content queries.** ## [View queries](https://angular.dev/#view-queries) View queries retrieve results from the elements in the component's _view_ — the elements defined in the component's own template. You can query for a single result with the `viewChild` function. @Component({ selector: 'custom-card-header', /*...*/})export class CustomCardHeader { text: string;}@Component({ selector: 'custom-card', template: '<custom-card-header>Visit sunny California!</custom-card-header>',})export class CustomCard { header = viewChild(CustomCardHeader); headerText = computed(() => this.header()?.text);} In this example, the `CustomCard` component queries for a child `CustomCardHeader` and uses the result in a `computed`. If the query does not find a result, its value is `undefined`. This may occur if the target element is hidden by `@if`. Angular keeps the result of `viewChild` up to date as your application state changes. You can also query for multiple results with the `viewChildren` function. @Component({ selector: 'custom-card-action', /*...*/})export class CustomCardAction { text: string;}@Component({ selector: 'custom-card', template: ` <custom-card-action>Save</custom-card-action> <custom-card-action>Cancel</custom-card-action> `,})export class CustomCard { actions = viewChildren(CustomCardAction); actionsTexts = computed(() => this.actions().map(action => action.text);} `viewChildren` creates a signal with an `Array` of the query results. **Queries never pierce through component boundaries.** View queries can only retrieve results from the component's template. ## [Content queries](https://angular.dev/#content-queries) Content queries retrieve results from the elements in the component's _content_— the elements nested inside the component in the template where it's used. You can query for a single result with the `contentChild` function. @Component({ selector: 'custom-toggle', /*...*/})export class CustomToggle { text: string;}@Component({ selector: 'custom-expando', /*...*/})export class CustomExpando { toggle = contentChild(CustomToggle); toggleText = computed(() => this.toggle()?.text);}@Component({ /* ... */ // CustomToggle is used inside CustomExpando as content. template: ` <custom-expando> <custom-toggle>Show</custom-toggle> </custom-expando> `})export class UserProfile { } In this example, the `CustomExpando` component queries for a child `CustomToggle` and accesses the result in a `computed`. If the query does not find a result, its value is `undefined`. This may occur if the target element is absent or hidden by `@if`. Angular keeps the result of `contentChild` up to date as your application state changes. By default, content queries find only _direct_ children of the component and do not traverse into descendants. You can also query for multiple results with the `contentChildren` function. @Component({ selector: 'custom-menu-item', /*...*/})export class CustomMenuItem { text: string;}@Component({ selector: 'custom-menu', /*...*/})export class CustomMenu { items = contentChildren(CustomMenuItem); itemTexts = computed(() => this.items().map(item => item.text));}@Component({ selector: 'user-profile', template: ` <custom-menu> <custom-menu-item>Cheese</custom-menu-item> <custom-menu-item>Tomato</custom-menu-item> </custom-menu> `})export class UserProfile { } `contentChildren` creates a signal with an `Array` of the query results. **Queries never pierce through component boundaries.** Content queries can only retrieve results from the same template as the component itself. ## [Required queries](https://angular.dev/#required-queries) If a child query (`viewChild` or `contentChild`) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like `@if` or `@for`. Because of this, the child queries return a signal that include `undefined` in their value type. In some cases, especially with `viewChild`, you know with certainty that a specific child is always available. In other cases, you may want to strictly enforce that a specific child is present. For these cases, you can use a _required query_. @Component({/* ... */})export class CustomCard { header = viewChild.required(CustomCardHeader); body = contentChild.required(CustomCardBody);} If a required query does not find a matching result, Angular reports an error. Because this guarantees that a result is available, required queries do not automatically include `undefined` in the signal's value type. ## [Query locators](https://angular.dev/#query-locators) This first parameter for each query decorator is its **locator**. Most of the time, you want to use a component or directive as your locator. You can alternatively specify a string locator corresponding to a [template reference variable](https://angular.dev/guide/templates/variables#template-reference-variables). @Component({ /*...*/ template: ` <button #save>Save</button> <button #cancel>Cancel</button> `})export class ActionBar { saveButton = viewChild<ElementRef<HTMLButtonElement>>('save');} If more than one element defines the same template reference variable, the query retrieves the first matching element. Angular does not support CSS selectors as query locators. ### [Queries and the injector tree](https://angular.dev/#queries-and-the-injector-tree) **TIP:** See [Dependency Injection](https://angular.dev/guide/di) for background on providers and Angular's injection tree. For more advanced cases, you can use any `ProviderToken` as a locator. This lets you locate elements based on component and directive providers. const SUB_ITEM = new InjectionToken<string>('sub-item');@Component({ /*...*/ providers: [{provide: SUB_ITEM, useValue: 'special-item'}],})export class SpecialItem { }@Component({/*...*/})export class CustomList { subItemType = contentChild(SUB_ITEM);} The above example uses an `InjectionToken` as a locator, but you can use any `ProviderToken` to locate specific elements. ## [Query options](https://angular.dev/#query-options) All query functions accept an options object as a second parameter. These options control how the query finds its results. ### [Reading specific values from an element's injector](https://angular.dev/#reading-specific-values-from-an-elements-injector) By default, the query locator indicates both the element you're searching for and the value retrieved. You can alternatively specify the `read` option to retrieve a different value from the element matched by the locator. @Component({/*...*/})export class CustomExpando { toggle = contentChild(ExpandoContent, {read: TemplateRef});} The above example, locates an element with the directive `ExpandoContent` and retrieves the `TemplateRef` associated with that element. Developers most commonly use `read` to retrieve `ElementRef` and `TemplateRef`. ### [Content descendants](https://angular.dev/#content-descendants) By default, content queries find only _direct_ children of the component and do not traverse into descendants. @Component({ selector: 'custom-expando', /*...*/})export class CustomExpando { toggle = contentChild(CustomToggle);}@Component({ selector: 'user-profile', template: ` <custom-expando> <some-other-component> <!-- custom-toggle will not be found! --> <custom-toggle>Show</custom-toggle> </some-other-component> </custom-expando> `})export class UserProfile { } In the example above, `CustomExpando` cannot find `<custom-toggle>` because it is not a direct child of `<custom-expando>`. By setting `descendants: true`, you configure the query to traverse all descendants in the same template. Queries, however, _never_ pierce into components to traverse elements in other templates. View queries do not have this option because they _always_ traverse into descendants. ## [Decorator-based queries](https://angular.dev/#decorator-based-queries) **TIP:** While the Angular team recommends using the signal-based query function for new projects, the original decorator-based query APIs remain fully supported. You can alternatively declare queries by adding the corresponding decorator to a property. Decorator-based queries behave the same way as signal-based queries except as described below. ### [View queries](https://angular.dev/#view-queries-1) You can query for a single result with the `@ViewChild` decorator. @Component({ selector: 'custom-card-header', /*...*/})export class CustomCardHeader { text: string;}@Component({ selector: 'custom-card', template: '<custom-card-header>Visit sunny California!</custom-card-header>',})export class CustomCard { @ViewChild(CustomCardHeader) header: CustomCardHeader; ngAfterViewInit() { console.log(this.header.text); }} In this example, the `CustomCard` component queries for a child `CustomCardHeader` and accesses the result in `ngAfterViewInit`. Angular keeps the result of `@ViewChild` up to date as your application state changes. **View query results become available in the `ngAfterViewInit` lifecycle method**. Before this point, the value is `undefined`. See the [Lifecycle](https://angular.dev/guide/components/lifecycle) section for details on the component lifecycle. You can also query for multiple results with the `@ViewChildren` decorator. @Component({ selector: 'custom-card-action', /*...*/})export class CustomCardAction { text: string;}@Component({ selector: 'custom-card', template: ` <custom-card-action>Save</custom-card-action> <custom-card-action>Cancel</custom-card-action> `,})export class CustomCard { @ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>; ngAfterViewInit() { this.actions.forEach(action => { console.log(action.text); }); }} `@ViewChildren` creates a `QueryList` object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property. ### [Content queries](https://angular.dev/#content-queries-1) You can query for a single result with the `@ContentChild` decorator. @Component({ selector: 'custom-toggle', /*...*/})export class CustomToggle { text: string;}@Component({ selector: 'custom-expando', /*...*/})export class CustomExpando { @ContentChild(CustomToggle) toggle: CustomToggle; ngAfterContentInit() { console.log(this.toggle.text); }}@Component({ selector: 'user-profile', template: ` <custom-expando> <custom-toggle>Show</custom-toggle> </custom-expando> `})export class UserProfile { } In this example, the `CustomExpando` component queries for a child `CustomToggle` and accesses the result in `ngAfterContentInit`. Angular keeps the result of `@ContentChild` up to date as your application state changes. **Content query results become available in the `ngAfterContentInit` lifecycle method**. Before this point, the value is `undefined`. See the [Lifecycle](https://angular.dev/guide/components/lifecycle) section for details on the component lifecycle. You can also query for multiple results with the `@ContentChildren` decorator. @Component({ selector: 'custom-menu-item', /*...*/})export class CustomMenuItem { text: string;}@Component({ selector: 'custom-menu', /*...*/})export class CustomMenu { @ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>; ngAfterContentInit() { this.items.forEach(item => { console.log(item.text); }); }}@Component({ selector: 'user-profile', template: ` <custom-menu> <custom-menu-item>Cheese</custom-menu-item> <custom-menu-item>Tomato</custom-menu-item> </custom-menu> `})export class UserProfile { } `@ContentChildren` creates a `QueryList` object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property. ### [Decorator-based query options](https://angular.dev/#decorator-based-query-options) All query decorators accept an options object as a second parameter. These options work the same way as signal-based queries except where described below. ### [Static queries](https://angular.dev/#static-queries) `@ViewChild` and `@ContentChild` decorators accept the `static` option. @Component({ selector: 'custom-card', template: '<custom-card-header>Visit sunny California!</custom-card-header>',})export class CustomCard { @ViewChild(CustomCardHeader, {static: true}) header: CustomCardHeader; ngOnInit() { console.log(this.header.text); }} By setting `static: true`, you guarantee to Angular that the target of this query is _always_ present and is not conditionally rendered. This makes the result available earlier, in the `ngOnInit` lifecycle method. Static query results do not update after initialization. The `static` option is not available for `@ViewChildren` and `@ContentChildren` queries. ### [Using QueryList](https://angular.dev/#using-querylist) `@ViewChildren` and `@ContentChildren` both provide a `QueryList` object that contains a list of results. `QueryList` offers a number of convenience APIs for working with results in an array-like manner, such as `map`, `reduce`, and `forEach`. You can get an array of the current results by calling `toArray`. You can subscribe to the `changes` property to do something any time the results change. ## [Common query pitfalls](https://angular.dev/#common-query-pitfalls) When using queries, common pitfalls can make your code harder to understand and maintain. Always maintain a single source of truth for state shared between multiple components. This avoids scenarios where repeated state in different components becomes out of sync. Avoid directly writing state to child components. This pattern can lead to brittle code that is hard to understand and is prone to [ExpressionChangedAfterItHasBeenChecked](https://angular.dev/errors/NG0100) errors. Never directly write state to parent or ancestor components. This pattern can lead to brittle code that is hard to understand and is prone to [ExpressionChangedAfterItHasBeenChecked](https://angular.dev/errors/NG0100) errors. --- ## Page: https://angular.dev/guide/components/dom-apis **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. Angular handles most DOM creation, updates, and removals for you. However, you might rarely need to directly interact with a component's DOM. Components can inject ElementRef to get a reference to the component's host element: @Component({...})export class ProfilePhoto { constructor() { const elementRef = inject(ElementRef); console.log(elementRef.nativeElement); }} The `nativeElement` property references the host [Element](https://developer.mozilla.org/docs/Web/API/Element) instance. You can use Angular's `afterRender` and `afterNextRender` functions to register a **render callback** that runs when Angular has finished rendering the page. @Component({...})export class ProfilePhoto { constructor() { const elementRef = inject(ElementRef); afterRender(() => { // Focus the first input element in this component. elementRef.nativeElement.querySelector('input')?.focus(); }); }} `afterRender` and `afterNextRender` must be called in an _injection context_, typically a component's constructor. **Avoid direct DOM manipulation whenever possible.** Always prefer expressing your DOM's structure in component templates and updating that DOM with bindings. **Render callbacks never run during server-side rendering or build-time pre-rendering.** **Never directly manipulate the DOM inside of other Angular lifecycle hooks**. Angular does not guarantee that a component's DOM is fully rendered at any point other than in render callbacks. Further, reading or modifying the DOM during other lifecycle hooks can negatively impact page performance by causing [layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). ## [Using a component's renderer](https://angular.dev/#using-a-components-renderer) Components can inject an instance of `Renderer2` to perform certain DOM manipulations that are tied to other Angular features. Any DOM elements created by a component's `Renderer2` participate in that component's [style encapsulation](https://angular.dev/guide/components/styling#style-scoping). Certain `Renderer2` APIs also tie into Angular's animation system. You can use the `setProperty` method to update synthetic animation properties and the `listen` method to add event listeners for synthetic animation events. See the [Animations](https://angular.dev/guide/animations) guide for details. Aside from these two narrow use-cases, there is no difference between using `Renderer2` and native DOM APIs. `Renderer2` APIs do not support DOM manipulation in server-side rendering or build-time pre-rendering contexts. ## [When to use DOM APIs](https://angular.dev/#when-to-use-dom-apis) While Angular handles most rendering concerns, some behaviors may still require using DOM APIs. Some common use cases include: * Managing element focus * Measuring element geometry, such as with `getBoundingClientRect` * Reading an element's text content * Setting up native observers such as [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver), [`ResizeObserver`](https://developer.mozilla.org/docs/Web/API/ResizeObserver), or [`IntersectionObserver`](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API). Avoid inserting, removing, and modifying DOM elements. In particular, **never directly set an element's `innerHTML` property**, which can make your application vulnerable to [cross-site scripting (XSS) exploits](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting). Angular's template bindings, including bindings for `innerHTML`, include safeguards that help protect against XSS attacks. See the [Security guide](https://angular.dev/best-practices/security) for details. --- ## Page: https://angular.dev/guide/components/inheritance **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. Angular components are TypeScript classes and participate in standard JavaScript inheritance semantics. A component can extend any base class: export class ListboxBase { value: string;}@Component({ ... })export class CustomListbox extends ListboxBase { // CustomListbox inherits the `value` property.} ## [Extending other components and directives](https://angular.dev/#extending-other-components-and-directives) When a component extends another component or a directive, it inherits some of the metadata defined in the base class's decorator and the base class's decorated members. This includes host bindings, inputs, outputs, lifecycle methods. @Component({ selector: 'base-listbox', template: ` ... `, host: { '(keydown)': 'handleKey($event)', },})export class ListboxBase { @Input() value: string; handleKey(event: KeyboardEvent) { /* ... */ }}@Component({ selector: 'custom-listbox', template: ` ... `, host: { '(click)': 'focusActiveOption()', },})export class CustomListbox extends ListboxBase { @Input() disabled = false; focusActiveOption() { /* ... */ }} In the example above, `CustomListbox` inherits all the information associated with `ListboxBase`, overriding the selector and template with its own values. `CustomListbox` has two inputs (`value` and `disabled`) and two event listeners (`keydown` and `click`). Child classes end up with the _union_ of all of their ancestors' inputs, outputs, and host bindings and their own. ### [Forwarding injected dependencies](https://angular.dev/#forwarding-injected-dependencies) If a base class injects dependencies as constructor parameters, the child class must explicitly class these dependencies to `super`. @Component({ ... })export class ListboxBase { constructor(private element: ElementRef) { }}@Component({ ... })export class CustomListbox extends ListboxBase { constructor(element: ElementRef) { super(element); }} ### [Overriding lifecycle methods](https://angular.dev/#overriding-lifecycle-methods) If a base class defines a lifecycle method, such as `ngOnInit`, a child class that also implements `ngOnInit` _overrides_ the base class's implementation. If you want to preserve the base class's lifecycle method, explicitly call the method with `super`: @Component({ ... })export class ListboxBase { protected isInitialized = false; ngOnInit() { this.isInitialized = true; }}@Component({ ... })export class CustomListbox extends ListboxBase { override ngOnInit() { super.ngOnInit(); /* ... */ }} --- ## Page: https://angular.dev/guide/components/programmatic-rendering **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. In addition to using a component directly in a template, you can also dynamically render components. There are two main ways to dynamically render a component: in a template with `NgComponentOutlet`, or in your TypeScript code with `ViewContainerRef`. ## [Using NgComponentOutlet](https://angular.dev/#using-ngcomponentoutlet) `NgComponentOutlet` is a structural directive that dynamically renders a given component in a template. @Component({ ... })export class AdminBio { /* ... */ }@Component({ ... })export class StandardBio { /* ... */ }@Component({ ..., template: ` <p>Profile for {{user.name}}</p> <ng-container *ngComponentOutlet="getBioComponent()" /> `})export class CustomDialog { @Input() user: User; getBioComponent() { return this.user.isAdmin ? AdminBio : StandardBio; }} See the [NgComponentOutlet API reference](https://angular.dev/api/common/NgComponentOutlet) for more information on the directive's capabilities. ## [Using ViewContainerRef](https://angular.dev/#using-viewcontainerref) A **view container** is a node in Angular's component tree that can contain content. Any component or directive can inject `ViewContainerRef` to get a reference to a view container corresponding to that component or directive's location in the DOM. You can use the `createComponent`method on `ViewContainerRef` to dynamically create and render a component. When you create a new component with a `ViewContainerRef`, Angular appends it into the DOM as the next sibling of the component or directive that injected the `ViewContainerRef`. @Component({ selector: 'leaf-content', template: ` This is the leaf content `,})export class LeafContent {}@Component({ selector: 'outer-container', template: ` <p>This is the start of the outer container</p> <inner-item /> <p>This is the end of the outer container</p> `,})export class OuterContainer {}@Component({ selector: 'inner-item', template: ` <button (click)="loadContent()">Load content</button> `,})export class InnerItem { private viewContainer = inject(ViewContainerRef); loadContent() { this.viewContainer.createComponent(LeafContent); }} In the example above, clicking the "Load content" button results in the following DOM structure <outer-container> <p>This is the start of the outer container</p> <inner-item> <button>Load content</button> </inner-item> <leaf-content>This is the leaf content</leaf-content> <p>This is the end of the outer container</p></outer-container> ## [Lazy-loading components](https://angular.dev/#lazy-loading-components) You can use both of the approaches described above, `NgComponentOutlet` and `ViewContainerRef`, to render components that are lazy-loaded with a standard JavaScript [dynamic import](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import). @Component({ ..., template: ` <section> <h2>Basic settings</h2> <basic-settings /> </section> <section> <h2>Advanced settings</h2> <button (click)="loadAdvanced()" *ngIf="!advancedSettings"> Load advanced settings </button> <ng-container *ngComponentOutlet="advancedSettings" /> </section> `})export class AdminSettings { advancedSettings: {new(): AdvancedSettings} | undefined; async loadAdvanced() { const { AdvancedSettings } = await import('path/to/advanced_settings.js'); this.advancedSettings = AdvancedSettings; }} The example above loads and displays the `AdvancedSettings` upon receiving a button click. --- ## Page: https://angular.dev/guide/components/advanced-configuration **TIP:** This guide assumes you've already read the [Essentials Guide](https://angular.dev/essentials). Read that first if you're new to Angular. ## [ChangeDetectionStrategy](https://angular.dev/#changedetectionstrategy) The `@Component` decorator accepts a `changeDetection` option that controls the component's **change detection mode**. There are two change detection mode options. **`ChangeDetectionStrategy.Default`** is, unsurprisingly, the default strategy. In this mode, Angular checks whether the component's DOM needs an update whenever any activity may have occurred application-wide. Activities that trigger this checking include user interaction, network response, timers, and more. **`ChangeDetectionStrategy.OnPush`** is an optional mode that reduces the amount of checking Angular needs to perform. In this mode, the framework only checks if a component's DOM needs an update when: * A component input has changes as a result of a binding in a template, or * An event listener in this component runs * The component is explicitly marked for check, via `ChangeDetectorRef.markForCheck` or something which wraps it, like `AsyncPipe`. Additionally, when an OnPush component is checked, Angular _also_ checks all of its ancestor components, traversing upwards through the application tree. ## [PreserveWhitespaces](https://angular.dev/#preservewhitespaces) By default, Angular removes and collapses superfluous whitespace in templates, most commonly from newlines and indentation. You can change this setting by explicitly setting `preserveWhitespaces` to `true` in a component's metadata. ## [Custom element schemas](https://angular.dev/#custom-element-schemas) By default, Angular throws an error when it encounters an unknown HTML element. You can disable this behavior for a component by including `CUSTOM_ELEMENTS_SCHEMA` in the `schemas` property in your component metadata. import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';@Component({ ..., schemas: [CUSTOM_ELEMENTS_SCHEMA], template: '<some-unknown-component></some-unknown-component>'})export class ComponentWithCustomElements { } Angular does not support any other schemas at this time. --- ## Page: https://angular.dev/guide/components/essentials ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/advanced-configuration ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/importing ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/templates/two-way-binding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/outputs ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/signals/inputs ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/ecosystem/rxjs-interop/output-interop ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/api/core/ng-template ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/selectors ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/queries#content-queries ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/errors/NG0100 ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/di/dependency-injection-context ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/dom-apis ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/templates/variables#template-reference-variables ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/lifecycle ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/components/styling#style-scoping ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/guide/animations ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/best-practices/security ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/components/api/common/NgComponentOutlet ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates **TIP:** Check out Angular's [Essentials](https://angular.dev/essentials/templates) before diving into this comprehensive guide. Every Angular component has a **template** that defines the [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) that the component renders onto the page. By using templates, Angular is able to automatically keep your page up-to-date as data changes. Templates are usually found within either the `template` property of a `*.component.ts` file or the `*.component.html` file. To learn more, check out the [in-depth components guide](https://angular.dev/guide/components). ## [How do templates work?](https://angular.dev/#how-do-templates-work) Templates are based on [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) syntax, with additional features such as built-in template functions, data binding, event listening, variables, and more. Angular compiles templates into JavaScript in order to build up an internal understanding of your application. One of the benefits of this are built-in rendering optimizations that Angular applies to your application automatically. ### [Differences from standard HTML](https://angular.dev/#differences-from-standard-html) Some differences between templates and standard HTML syntax include: * Comments in the template source code are not included in the rendered output * Component and directive elements can be self-closed (e.g., `<UserProfile />`) * Attributes with certain characters (i.e., `[]`, `()`, etc.) have special meaning to Angular. See [binding docs](https://angular.dev/guide/templates/binding) and [adding event listeners docs](https://angular.dev/guide/templates/event-listeners) for more information. * The `@` character has a special meaning to Angular for adding dynamic behavior, such as [control flow](https://angular.dev/guide/templates/control-flow), to templates. You can include a literal `@` character by escaping it as an HTML entity code (`@` or `@`). * Angular ignores and collapses unnecessary whitespace characters. See [whitespace in templates](https://angular.dev/guide/templates/whitespace) for more details. * Angular may add comment nodes to a page as placeholders for dynamic content, but developers can ignore these. In addition, while most HTML syntax is valid template syntax, Angular does not support `<script>` element in templates. For more information, see the [Security](https://angular.dev/best-practices/security) page. ## [What's next?](https://angular.dev/#whats-next) You might also be interested in the following: | Topics | Details | | --- | --- | | [Binding dynamic text, properties, and attributes](https://angular.dev/guide/templates/binding) | Bind dynamic data to text, properties and attributes. | | [Adding event listeners](https://angular.dev/guide/templates/event-listeners) | Respond to events in your templates. | | [Two-way binding](https://angular.dev/guide/templates/two-way-binding) | Simultaneously binds a value and propagate changes. | | [Control flow](https://angular.dev/guide/templates/control-flow) | Conditionally show, hide and repeat elements. | | [Pipes](https://angular.dev/guide/templates/pipes) | Transform data declaratively. | | [Slotting child content with ng-content](https://angular.dev/guide/templates/ng-content) | Control how components render content. | | [Create template fragments with ng-template](https://angular.dev/guide/templates/ng-template) | Declare a template fragment. | | [Grouping elements with ng-container](https://angular.dev/guide/templates/ng-container) | Group multiple elements together or mark a location for rendering. | | [Variables in templates](https://angular.dev/guide/templates/variables) | Learn about variable declarations. | | [Deferred loading with @defer](https://angular.dev/guide/templates/defer) | Create deferrable views with `@defer`. | | [Expression syntax](https://angular.dev/guide/templates/expression-syntax) | Learn similarities and differences between Angular expressions and standard JavaScript. | | [Whitespace in templates](https://angular.dev/guide/templates/whitespace) | Learn how Angular handles whitespace. | --- ## Page: https://angular.dev/guide/templates/binding In Angular, a **binding** creates a dynamic connection between a component's template and its data. This connection ensures that changes to the component's data automatically update the rendered template. ## [Render dynamic text with text interpolation](https://angular.dev/#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> In addition to evaluating the expression at first render, Angular also updates the rendered content when the expression's value changes. Continuing the theme example, if a user clicks on a button that changes the value of `theme` 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](https://angular.dev/#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](https://angular.dev/guide/components) instance, or a [directive](https://angular.dev/guide/directives) instance. ### [Native element properties](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#css-class-and-style-property-bindings) Angular supports additional features for binding CSS classes and CSS style properties to elements. ### [CSS classes](https://angular.dev/#css-classes) You can create a CSS class binding to conditionally add or remove a CSS class on an element based on whether the bound value is [truthy or falsy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). <!-- When `isExpanded` is truthy, add the `expanded` CSS class. --><ul [class.expanded]="isExpanded"> You can also bind directly to the `class` property. Angular accepts three types of value: | Description of `class` value | TypeScript type | | --- | --- | | A string containing one or more CSS classes separated by spaces | `string` | | An array of CSS class strings | `string[]` | | An object where each property name is a CSS class name and each corresponding value determines whether that class is applied to the element, based on truthiness. | `Record<string, any>` | @Component({ template: ` <ul [class]="listClasses"> ... </ul> <section [class]="sectionClasses"> ... </section> <button [class]="buttonClasses"> ... </button> `, ...})export class UserProfile { listClasses = 'full-width outlined'; sectionClasses = ['expandable', 'elevated']; buttonClasses = { 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 = 'box'; isExpanded = 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. ### [CSS style properties](https://angular.dev/#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 = 'display: flex; padding: 8px'; sectionStyles = { 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. --- ## Page: https://angular.dev/guide/templates/event-listeners Angular supports defining event listeners on an element in your template by specifying the event name inside parentheses along with a statement that runs every time the event occurs. ## [Listening to native events](https://angular.dev/#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](https://developer.mozilla.org/en-US/docs/Web/API/Element#events). ## [Accessing the event argument](https://angular.dev/#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](https://angular.dev/#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](https://developer.mozilla.org/docs/Web/API/UI_Events/Keyboard_event_key_values). Angular also allows you to specify [Code values for keyboard events](https://developer.mozilla.org/docs/Web/API/UI_Events/Keyboard_event_code_values) by providing a built-in `code` suffix. <!-- Matches alt and left shift --><input type="text" (keydown.code.alt.shiftleft)="updateField($event)" /> This can be useful for handling keyboard events consistently across different operating systems. For example, when using the Alt key on MacOS devices, the `key` property reports the key based on the character already modified by the Alt key. This means that a combination like Alt + S reports a `key` value of `'ß'`. The `code` property, however, corresponds to the physical or virtual button pressed rather than the character produced. --- ## Page: https://angular.dev/guide/templates/two-way-binding **Two way binding** is a shorthand to simultaneously bind a value into an element, while also giving that element the ability to propagate changes back through this binding. ## [Syntax](https://angular.dev/#syntax) The syntax for two-way binding is a combination of square brackets and parentheses, `[()]`. It combines the syntax from property binding, `[]`, and the syntax from event binding, `()`. The Angular community informally refers to this syntax as "banana-in-a-box". ## [Two-way binding with form controls](https://angular.dev/#two-way-binding-with-form-controls) Developers commonly use two-way binding to keep component data in sync with a form control as a user interacts with the control. For example, when a user fills out a text input, it should update the state in the component. The following example dynamically updates the `firstName` attribute on the page: import { Component } from '@angular/core';import { FormsModule } from '@angular/forms';@Component({ imports: [FormsModule], template: ` <main> <h2>Hello {{ firstName }}!</h2> <input type="text" [(ngModel)]="firstName" /> </main> `})export class AppComponent { firstName = 'Ada';} To use two-way binding with native form controls, you need to: 1. Import the `FormsModule` from `@angular/forms` 2. Use the `ngModel` directive with the two-way binding syntax (e.g., `[(ngModel)]`) 3. Assign it the state that you want it to update (e.g., `firstName`) Once that is set up, Angular will ensure that any updates in the text input will reflect correctly inside of the component state! Learn more about [`NgModel`](https://angular.dev/guide/directives#displaying-and-updating-properties-with-ngmodel) in the official docs. ## [Two-way binding between components](https://angular.dev/#two-way-binding-between-components) Leveraging two-way binding between a parent and child component requires more configuration compared to form elements. Here is an example where the `AppComponent` is responsible for setting the initial count state, but the logic for updating and rendering the UI for the counter primarily resides inside its child `CounterComponent`. // ./app.component.tsimport { Component } from '@angular/core';import { CounterComponent } from './counter/counter.component';@Component({ selector: 'app-root', imports: [CounterComponent], template: ` <main> <h1>Counter: {{ initialCount }}</h1> <app-counter [(count)]="initialCount"></app-counter> </main> `,})export class AppComponent { initialCount = 18;} // './counter/counter.component.ts';import { Component, model } from '@angular/core';@Component({ selector: 'app-counter', template: ` <button (click)="updateCount(-1)">-</button> <span>{{ count() }}</span> <button (click)="updateCount(+1)">+</button> `,})export class CounterComponent { count = model<number>(0); updateCount(amount: number): void { this.count.update(currentCount => currentCount + amount); }} ### [Enabling two-way binding between components](https://angular.dev/#enabling-two-way-binding-between-components) If we break down the example above to its core, each two-way binding for components requires the following: The child component must contain a `model` property. Here is a simplified example: // './counter/counter.component.ts';import { Component, model } from '@angular/core';@Component({ // Omitted for brevity })export class CounterComponent { count = model<number>(0); updateCount(amount: number): void { this.count.update(currentCount => currentCount + amount); }} The parent component must: 1. Wrap the `model` property name in the two-way binding syntax. 2. Assign a property or a signal to the `model` property. Here is a simplified example: // ./app.component.tsimport { Component } from '@angular/core';import { CounterComponent } from './counter/counter.component';@Component({ selector: 'app-root', imports: [CounterComponent], template: ` <main> <app-counter [(count)]="initialCount"></app-counter> </main> `,})export class AppComponent { initialCount = 18;} --- ## Page: https://angular.dev/guide/templates/control-flow Angular templates support control flow blocks that let you conditionally show, hide, and repeat elements. **NOTE:** This was previously accomplished with the \*ngIf, \*ngFor, and \*ngSwitch directives. ## [Conditionally display content with `@if`, `@else-if` and `@else`](https://angular.dev/#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](https://angular.dev/#referencing-the-conditional-expressions-result) The `@if` conditional supports saving the result of the conditional expression into a variable for reuse inside of the block. @if (user.profile.settings.startDate; as startDate) { {{ startDate }}} This can be useful for referencing longer expressions that would be easier to read and maintain within the template. ## [Repeat content with the `@for` block](https://angular.dev/#repeat-content-with-the-for-block) The `@for` block loops through a collection and repeatedly renders the content of a block. The collection can be any JavaScript [iterable](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols), but Angular has additional performance optimizations for `Array` values. A typical `@for` loop looks like: @for (item of items; track item.id) { {{ item.name }}} Angular's `@for` block does not support flow-modifying statements like JavaScript's `continue` or `break`. ### [Why is `track` in `@for` blocks important?](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/templates/pipes ## [Overview](https://angular.dev/#overview) Pipes are a special operator in Angular template expressions that allows you to transform data declaratively in your template. Pipes let you declare a transformation function once and then use that transformation across multiple templates. Angular pipes use the vertical bar character (`|`), inspired by the [Unix pipe](https://en.wikipedia.org/wiki/Pipeline_\(Unix\)). **NOTE:** Angular's pipe syntax deviates from standard JavaScript, which uses the vertical bar character for the [bitwise OR operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR). Angular template expressions do not support bitwise operators. Here is an example using some built-in pipes that Angular provides: import { Component } from '@angular/core';import { CurrencyPipe, DatePipe, TitleCasePipe } from '@angular/common';@Component({ selector: 'app-root', imports: [CurrencyPipe, DatePipe, TitleCasePipe], template: ` <main> <!-- Transform the company name to title-case and transform the purchasedOn date to a locale-formatted string --><h1>Purchases from {{ company | titlecase }} on {{ purchasedOn | date }}</h1> <!-- Transform the amount to a currency-formatted string --> <p>Total: {{ amount | currency }}</p> </main> `,})export class ShoppingCartComponent { amount = 123.45; company = 'acme corporation'; purchasedOn = '2024-07-08';} When Angular renders the component, it will ensure that the appropriate date format and currency is based on the locale of the user. If the user is in the United States, it would render: <main> <h1>Purchases from Acme Corporation on Jul 8, 2024</h1> <p>Total: $123.45</p></main> See the [in-depth guide on i18n](https://angular.dev/guide/i18n) to learn more about how Angular localizes values. ### [Built-in Pipes](https://angular.dev/#built-in-pipes) Angular includes a set of built-in pipes in the `@angular/common` package: | Name | Description | | --- | --- | | [`AsyncPipe`](https://angular.dev/api/common/AsyncPipe) | Read the value from a `Promise` or an RxJS `Observable`. | | [`CurrencyPipe`](https://angular.dev/api/common/CurrencyPipe) | Transforms a number to a currency string, formatted according to locale rules. | | [`DatePipe`](https://angular.dev/api/common/DatePipe) | Formats a `Date` value according to locale rules. | | [`DecimalPipe`](https://angular.dev/api/common/DecimalPipe) | Transforms a number into a string with a decimal point, formatted according to locale rules. | | [`I18nPluralPipe`](https://angular.dev/api/common/I18nPluralPipe) | Maps a value to a string that pluralizes the value according to locale rules. | | [`I18nSelectPipe`](https://angular.dev/api/common/I18nSelectPipe) | Maps a key to a custom selector that returns a desired value. | | [`JsonPipe`](https://angular.dev/api/common/JsonPipe) | Transforms an object to a string representation via `JSON.stringify`, intended for debugging. | | [`KeyValuePipe`](https://angular.dev/api/common/KeyValuePipe) | Transforms Object or Map into an array of key value pairs. | | [`LowerCasePipe`](https://angular.dev/api/common/LowerCasePipe) | Transforms text to all lower case. | | [`PercentPipe`](https://angular.dev/api/common/PercentPipe) | Transforms a number to a percentage string, formatted according to locale rules. | | [`SlicePipe`](https://angular.dev/api/common/SlicePipe) | Creates a new Array or String containing a subset (slice) of the elements. | | [`TitleCasePipe`](https://angular.dev/api/common/TitleCasePipe) | Transforms text to title case. | | [`UpperCasePipe`](https://angular.dev/api/common/UpperCasePipe) | Transforms text to all upper case. | ## [Using pipes](https://angular.dev/#using-pipes) Angular's pipe operator uses the vertical bar character (`|`), within a template expression. The pipe operator is a binary operator– the left-hand operand is the value passed to the transformation function, and the right side operand is the name of the pipe and any additional arguments (described below). <p>Total: {{ amount | currency }}</p> In this example, the value of `amount` is passed into the `CurrencyPipe` where the pipe name is `currency`. It then renders the default currency for the user’s locale. ### [Combining multiple pipes in the same expression](https://angular.dev/#combining-multiple-pipes-in-the-same-expression) You can apply multiple transformations to a value by using multiple pipe operators. Angular runs the pipes from left to right. The following example demonstrates a combination of pipes to display a localized date in all uppercase: <p>The event will occur on {{ scheduledOn | date | uppercase }}.</p> ### [Passing parameters to pipes](https://angular.dev/#passing-parameters-to-pipes) Some pipes accept parameters to configure the transformation. To specify a parameter, append the pipe name with a colon (`:`) followed by the parameter value. For example, the `DatePipe` is able to take parameters to format the date in a specific way. <p>The event will occur at {{ scheduledOn | date:'hh:mm' }}.</p> Some pipes may accept multiple parameters. You can specify additional parameter values separated by the colon character (`:`). For example, we can also pass a second optional parameter to control the timezone. <p>The event will occur at {{ scheduledOn | date:'hh:mm':'UTC' }}.</p> ## [How pipes work](https://angular.dev/#how-pipes-work) Conceptually, pipes are functions that accept an input value and return a transformed value. import { Component } from '@angular/core';import { CurrencyPipe} from '@angular/common';@Component({ selector: 'app-root', imports: [CurrencyPipe], template: ` <main> <p>Total: {{ amount | currency }}</p> </main> `,})export class AppComponent { amount = 123.45;} In this example: 1. `CurrencyPipe` is imported from `@angular/common` 2. `CurrencyPipe` is added to the `imports` array 3. The `amount` data is passed to the `currency` pipe ### [Pipe operator precedence](https://angular.dev/#pipe-operator-precedence) The pipe operator has lower precedence than other binary operators, including `+`, `-`, `*`, `/`, `%`, `&&`, `||`, and `??`. <!-- firstName and lastName are concatenated before the result is passed to the uppercase pipe -->{{ firstName + lastName | uppercase }} The pipe operator has higher precedence than the conditional (ternary) operator. {{ (isAdmin ? 'Access granted' : 'Access denied') | uppercase }} If the same expression were written without parentheses: {{ isAdmin ? 'Access granted' : 'Access denied' | uppercase }} It will be parsed instead as: {{ isAdmin ? 'Access granted' : ('Access denied' | uppercase) }} Always use parentheses in your expressions when operator precedence may be ambiguous. ### [Change detection with pipes](https://angular.dev/#change-detection-with-pipes) By default, all pipes are considered `pure`, which means that it only executes when a primitive input value (such as a `String`, `Number`, `Boolean`, or `Symbol`) or a changed object reference (such as `Array`, `Object`, `Function`, or `Date`). Pure pipes offer a performance advantage because Angular can avoid calling the transformation function if the passed value has not changed. As a result, this means that mutations to object properties or array items are not detected unless the entire object or array reference is replaced with a different instance. If you want this level of change detection, refer to [detecting changes within arrays or objects](https://angular.dev/#detecting-change-within-arrays-or-objects). ## [Creating custom pipes](https://angular.dev/#creating-custom-pipes) You can define a custom pipe by implementing a TypeScript class with the `@Pipe` decorator. A pipe must have two things: * A name, specified in the pipe decorator * A method named `transform` that performs the value transformation. The TypeScript class should additionally implement the `PipeTransform` interface to ensure that it satisfies the type signature for a pipe. Here is an example of a custom pipe that transforms strings to kebab case: // kebab-case.pipe.tsimport { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'kebabCase',})export class KebabCasePipe implements PipeTransform { transform(value: string): string { return value.toLowerCase().replace(/ /g, '-'); }} ### [Using the `@Pipe` decorator](https://angular.dev/#using-the-pipe-decorator) When creating a custom pipe, import `Pipe` from the `@angular/core` package and use it as a decorator for the TypeScript class. import { Pipe } from '@angular/core';@Pipe({ name: 'myCustomTransformation',})export class MyCustomTransformationPipe {} The `@Pipe` decorator requires a `name` that controls how the pipe is used in a template. ### [Naming convention for custom pipes](https://angular.dev/#naming-convention-for-custom-pipes) The naming convention for custom pipes consists of two conventions: * `name` - camelCase is recommended. Do not use hyphens. * `class name` - PascalCase version of the `name` with `Pipe` appended to the end ### [Implement the `PipeTransform` interface](https://angular.dev/#implement-the-pipetransform-interface) In addition to the `@Pipe` decorator, custom pipes should always implement the `PipeTransform` interface from `@angular/core`. import { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'myCustomTransformation',})export class MyCustomTransformationPipe implements PipeTransform {} Implementing this interface ensures that your pipe class has the correct structure. ### [Transforming the value of a pipe](https://angular.dev/#transforming-the-value-of-a-pipe) Every transformation is invoked by the `transform` method with the first parameter being the value being passed in and the return value being the transformed value. import { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'myCustomTransformation',})export class MyCustomTransformationPipe implements PipeTransform { transform(value: string): string { return `My custom transformation of ${value}.` }} ### [Adding parameters to a custom pipe](https://angular.dev/#adding-parameters-to-a-custom-pipe) You can add parameters to your transformation by adding additional parameters to the `transform` method: import { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'myCustomTransformation',})export class MyCustomTransformationPipe implements PipeTransform { transform(value: string, format: string): string { let msg = `My custom transformation of ${value}.` if (format === 'uppercase') { return msg.toUpperCase() } else { return msg } }} ### [Detecting change within arrays or objects](https://angular.dev/#detecting-change-within-arrays-or-objects) When you want a pipe to detect changes within arrays or objects, it must be marked as an impure function by passing the `pure` flag with a value of `false`. Avoid creating impure pipes unless absolutely necessary, as they can incur a significant performance penalty if used without care. import { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'joinNamesImpure', pure: false,})export class JoinNamesImpurePipe implements PipeTransform { transform(names: string[]): string { return names.join(); }} Angular developers often adopt the convention of including `Impure` in the pipe `name` and class name to indicate the potential performance pitfall to other developers. --- ## Page: https://angular.dev/guide/templates/ng-content `<ng-content>` is a special element that accepts markup or a template fragment and controls how components render content. It does not render a real DOM element. Here is an example of a `BaseButton` component that accepts any markup from its parent. // ./base-button/base-button.component.tsimport { Component } from '@angular/core';@Component({ selector: 'button[baseButton]', template: ` <ng-content /> `,})export class BaseButton {} // ./app.component.tsimport { Component } from '@angular/core';import { BaseButton } from './base-button/base-button.component.ts';@Component({ selector: 'app-root', imports: [BaseButton], template: ` <button baseButton> Next <span class="icon arrow-right" /> </button> `,})export class AppComponent {} For more detail, check out the [`<ng-content>` in-depth guide](https://angular.dev/guide/components/content-projection) for other ways you can leverage this pattern. --- ## Page: https://angular.dev/guide/templates/ng-template Inspired by the [native `<template>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template), the `<ng-template>` element lets you declare a **template fragment** – a section of content that you can dynamically or programmatically render. ## [Creating a template fragment](https://angular.dev/#creating-a-template-fragment) You can create a template fragment inside of any component template with the `<ng-template>` element: <p>This is a normal element</p><ng-template> <p>This is a template fragment</p></ng-template> When the above is rendered, the content of the `<ng-template>` element is not rendered on the page. Instead, you can get a reference to the template fragment and write code to dynamically render it. ### [Binding context for fragments](https://angular.dev/#binding-context-for-fragments) Template fragments may contain bindings with dynamic expressions: @Component({ /* ... */, template: `<ng-template>You've selected {{count}} items.</ng-template>`,})export class ItemCounter { count: number = 0;} Expressions or statements in a template fragment are evaluated against the component in which the fragment is declared, regardless of where the fragment is rendered. ## [Getting a reference to a template fragment](https://angular.dev/#getting-a-reference-to-a-template-fragment) You can get a reference to a template fragment in one of three ways: * By declaring a [template reference variable](https://angular.dev/guide/templates/variables#template-reference-variables) on the `<ng-template>` element * By querying for the fragment with [a component or directive query](https://angular.dev/guide/components/queries) * By injecting the fragment in a directive that's applied directly to an `<ng-template>` element. In all three cases, the fragment is represented by a [TemplateRef](https://angular.dev/api/core/TemplateRef) object. ### [Referencing a template fragment with a template reference variable](https://angular.dev/#referencing-a-template-fragment-with-a-template-reference-variable) You can add a template reference variable to an `<ng-template>` element to reference that template fragment in other parts of the same template file: <p>This is a normal element</p><ng-template #myFragment> <p>This is a template fragment</p></ng-template> You can then reference this fragment anywhere else in the template via the `myFragment` variable. ### [Referencing a template fragment with queries](https://angular.dev/#referencing-a-template-fragment-with-queries) You can get a reference to a template fragment using any [component or directive query API](https://angular.dev/guide/components/queries). For example, if your template has exactly one template fragment, you can query directly for the `TemplateRef` object with a `@ViewChild` query: @Component({ /* ... */, template: ` <p>This is a normal element</p> <ng-template> <p>This is a template fragment</p> </ng-template> `,})export class ComponentWithFragment { @ViewChild(TemplateRef) myFragment: TemplateRef<unknown> | undefined;} You can then reference this fragment in your component code or the component's template like any other class member. If a template contains multiple fragments, you can assign a name to each fragment by adding a template reference variable to each `<ng-template>` element and querying for the fragments based on that name: @Component({ /* ... */, template: ` <p>This is a normal element</p> <ng-template #fragmentOne> <p>This is one template fragment</p> </ng-template> <ng-template #fragmentTwo> <p>This is another template fragment</p> </ng-template> `,})export class ComponentWithFragment { // When querying by name, you can use the `read` option to specify that you want to get the // TemplateRef object associated with the element. @ViewChild('fragmentOne', {read: TemplateRef}) fragmentOne: TemplateRef<unknown> | undefined; @ViewChild('fragmentTwo', {read: TemplateRef}) fragmentTwo: TemplateRef<unknown> | undefined;} Again, you can then reference these fragments in your component code or the component's template like any other class members. ### [Injecting a template fragment](https://angular.dev/#injecting-a-template-fragment) A directive can inject a `TemplateRef` if that directive is applied directly to an `<ng-template>` element: @Directive({ selector: '[myDirective]'})export class MyDirective { private fragment = inject(TemplateRef);} <ng-template myDirective> <p>This is one template fragment</p></ng-template> You can then reference this fragment in your directive code like any other class member. ## [Rendering a template fragment](https://angular.dev/#rendering-a-template-fragment) Once you have a reference to a template fragment's `TemplateRef` object, you can render a fragment in one of two ways: in your template with the `NgTemplateOutlet` directive or in your TypeScript code with `ViewContainerRef`. ### [Using `NgTemplateOutlet`](https://angular.dev/#using-ngtemplateoutlet) The `NgTemplateOutlet` directive from `@angular/common` accepts a `TemplateRef` and renders the fragment as a **sibling** to the element with the outlet. You should generally use `NgTemplateOutlet` on an [`<ng-container>` element](https://angular.dev/guide/templates/ng-container). First, import `NgTemplateOutlet`: import { NgTemplateOutlet } from '@angular/common'; The following example declares a template fragment and renders that fragment to a `<ng-container>` element with `NgTemplateOutlet`: <p>This is a normal element</p><ng-template #myFragment> <p>This is a fragment</p></ng-template><ng-container *ngTemplateOutlet="myFragment"></ng-container> This example produces the following rendered DOM: <p>This is a normal element</p><p>This is a fragment</p> ### [Using `ViewContainerRef`](https://angular.dev/#using-viewcontainerref) A **view container** is a node in Angular's component tree that can contain content. Any component or directive can inject `ViewContainerRef` to get a reference to a view container corresponding to that component or directive's location in the DOM. You can use the `createEmbeddedView` method on `ViewContainerRef` to dynamically render a template fragment. When you render a fragment with a `ViewContainerRef`, Angular appends it into the DOM as the next sibling of the component or directive that injected the `ViewContainerRef`. The following example shows a component that accepts a reference to a template fragment as an input and renders that fragment into the DOM on a button click. @Component({ /* ... */, selector: 'component-with-fragment', template: ` <h2>Component with a fragment</h2> <ng-template #myFragment> <p>This is the fragment</p> </ng-template> <my-outlet [fragment]="myFragment" /> `,})export class ComponentWithFragment { }@Component({ /* ... */, selector: 'my-outlet', template: `<button (click)="showFragment()">Show</button>`,})export class MyOutlet { private viewContainer = inject(ViewContainerRef); @Input() fragment: TemplateRef<unknown> | undefined; showFragment() { if (this.fragment) { this.viewContainer.createEmbeddedView(this.fragment); } }} In the example above, clicking the "Show" button results in the following output: <component-with-fragment> <h2>Component with a fragment> <my-outlet> <button>Show</button> </my-outlet> <p>This is the fragment</p></component-with-fragment> ## [Passing parameters when rendering a template fragment](https://angular.dev/#passing-parameters-when-rendering-a-template-fragment) When declaring a template fragment with `<ng-template>`, you can additionally declare parameters accepted by the fragment. When you render a fragment, you can optionally pass a `context` object corresponding to these parameters. You can use data from this context object in binding expressions and statements, in addition to referencing data from the component in which the fragment is declared. Each parameter is written as an attribute prefixed with `let-` with a value matching a property name in the context object: <ng-template let-pizzaTopping="topping"> <p>You selected: {{pizzaTopping}}</p></ng-template> ### [Using `NgTemplateOutlet`](https://angular.dev/#using-ngtemplateoutlet-1) You can bind a context object to the `ngTemplateOutletContext` input: <ng-template #myFragment let-pizzaTopping="topping"> <p>You selected: {{pizzaTopping}}</p></ng-template><ng-container [ngTemplateOutlet]="myFragment" [ngTemplateOutletContext]="{topping: 'onion'}"/> ### [Using `ViewContainerRef`](https://angular.dev/#using-viewcontainerref-1) You can pass a context object as the second argument to `createEmbeddedView`: this.viewContainer.createEmbeddedView(this.myFragment, {topping: 'onion'}); ## [Structural directives](https://angular.dev/#structural-directives) A **structural directive** is any directive that: * Injects `TemplateRef` * Injects `ViewContainerRef` and programmatically renders the injected `TemplateRef` Angular supports a special convenience syntax for structural directives. If you apply the directive to an element and prefix the directive's selector with an asterisk (`*`) character, Angular interprets the entire element and all of its content as a template fragment: <section *myDirective> <p>This is a fragment</p></section> This is equivalent to: <ng-template myDirective> <section> <p>This is a fragment</p> </section></ng-template> Developers typically use structural directives to conditionally render fragments or render fragments multiple times. For more details, see [Structural Directives](https://angular.dev/guide/directives/structural-directives). ## [Additional resources](https://angular.dev/#additional-resources) For examples of how `ng-template` is used in other libraries, check out: * [Tabs from Angular Material](https://material.angular.io/components/tabs/overview) - nothing gets rendered into the DOM until the tab is activated * [Table from Angular Material](https://material.angular.io/components/table/overview) - allows developers to define different ways to render data --- ## Page: https://angular.dev/guide/templates/ng-container `<ng-container>` is a special element in Angular that groups multiple elements together or marks a location in a template without rendering a real element in the DOM. <!-- Component template --><section> <ng-container> <h3>User bio</h3> <p>Here's some info about the user</p> </ng-container></section> <!-- Rendered DOM --><section> <h3>User bio</h3> <p>Here's some info about the user</p></section> You can apply directives to `<ng-container>` to add behaviors or configuration to a part of your template. Angular ignores all attribute bindings and event listeners applied to `<ng-container>`, including those applied via directive. ## [Using `<ng-container>` to display dynamic contents](https://angular.dev/#using-ng-container-to-display-dynamic-contents) `<ng-container>` can act as a placeholder for rendering dynamic content. ### [Rendering components](https://angular.dev/#rendering-components) You can use Angular's built-in `NgComponentOutlet` directive to dynamically render a component to the location of the `<ng-container>`. @Component({ template: ` <h2>Your profile</h2> <ng-container [ngComponentOutlet]="profileComponent()" /> `})export class UserProfile { isAdmin = input(false); profileComponent = computed(() => this.isAdmin() ? AdminProfile : BasicUserProfile);} In the example above, the `NgComponentOutlet` directive dynamically renders either `AdminProfile` or `BasicUserProfile` in the location of the `<ng-container>` element. ### [Rendering template fragments](https://angular.dev/#rendering-template-fragments) You can use Angular's built-in `NgTemplateOutlet` directive to dynamically render a template fragment to the location of the `<ng-container>`. @Component({ template: ` <h2>Your profile</h2> <ng-container [ngTemplateOutlet]="profileTemplate()" /> <ng-template #admin>This is the admin profile</ng-template> <ng-template #basic>This is the basic profile</ng-template> `})export class UserProfile { isAdmin = input(false); adminTemplate = viewChild('admin', {read: TemplateRef}); basicTemplate = viewChild('basic', {read: TemplateRef}); profileTemplate = computed(() => this.isAdmin() ? this.adminTemplate() : this.basicTemplate());} In the example above, the `ngTemplateOutlet` directive dynamically renders one of two template fragments in the location of the `<ng-container>` element. For more information regarding NgTemplateOutlet, see the [NgTemplateOutlets API documentation page](https://angular.dev/api/common/NgTemplateOutlet). ## [Using `<ng-container>` with structural directives](https://angular.dev/#using-ng-container-with-structural-directives) You can also apply structural directives to `<ng-container>` elements. Common examples of this include `ngIf`and `ngFor`. <ng-container *ngIf="permissions == 'admin'"> <h1>Admin Dashboard</h1> <admin-infographic></admin-infographic></ng-container><ng-container *ngFor="let item of items; index as i; trackBy: trackByFn"> <h2>{{ item.title }}</h2> <p>{{ item.description }}</p></ng-container> ## [Using `<ng-container>` for injection](https://angular.dev/#using-ng-container-for-injection) See the Dependency Injection guide for more information on Angular's dependency injection system. When you apply a directive to `<ng-container>`, descendant elements can inject the directive or anything that the directive provides. Use this when you want to declaratively provide a value to a specific part of your template. @Directive({ selector: '[theme]',})export class Theme { // Create an input that accepts 'light' or 'dark`, defaulting to 'light'. mode = input<'light' | 'dark'>('light');} <ng-container theme="dark"> <profile-pic /> <user-bio /></ng-container> In the example above, the `ProfilePic` and `UserBio` components can inject the `Theme` directive and apply styles based on its `mode`. --- ## Page: https://angular.dev/guide/templates/variables Angular has two types of variable declarations in templates: local template variables and template reference variables. ## [Local template variables with `@let`](https://angular.dev/#local-template-variables-with-let) Angular's `@let` syntax allows you to define a local variable and re-use it across a template, similar to the [JavaScript `let` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let). ### [Using `@let`](https://angular.dev/#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](https://angular.dev/templates/bindings). @let name = user.name;@let greeting = 'Hello, ' + name;@let data = data$ | async;@let pi = 3.1459;@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`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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 --> }}<div *ngIf="condition"> {{topLevel + insideDiv}} <!-- Valid --> @let nestedNgIf = value; <div *ngIf="condition"> {{topLevel + insideDiv + nestedNgIf}} <!-- Valid --> </div></div>{{nested}} <!-- Error, not hoisted from @if -->{{nestedNgIf}} <!-- Error, not hoisted from *ngIf --> ### [Full syntax](https://angular.dev/#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](https://angular.dev/#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: * a DOM element within a template (including [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)) * an Angular component or directive * a [TemplateRef](https://angular.dev/api/core/TemplateRef) from an [ng-template](https://angular.dev/api/core/ng-template) 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/directives/structural-directives#asterisk) in [Structural directives](https://angular.dev/guide/directives/structural-directives). <!-- The `myFragment` variable is assigned the `TemplateRef` instance corresponding to this template fragment. --><ng-template #myFragment> <p>This is a template fragment</p></ng-template> If you declare the variable on any other displayed element, the variable refers to the `HTMLElement` instance. <!-- The "taskInput" variable refers to the HTMLInputElement instance. --><input #taskInput placeholder="Enter task name"> #### [Assigning a reference to an Angular directive](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/components/queries). When you want to query for a specific element in a template, you can declare a template variable on that element and then query for the element based on the variable name. <input #description value="Original description"> @Component({ /* ... */, template: `<input #description value="Original description">`,})export class AppComponent { // Query for the input element based on the template variable name. @ViewChild('description') input: ElementRef | undefined;} See [Referencing children with queries](https://angular.dev/guide/components/queries) for more information on queries. --- ## Page: https://angular.dev/guide/templates/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?](https://angular.dev/#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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statement for each component, directive, and pipe used in the `@defer` block. The main content of the block renders after all the imports resolve. Angular does not guarantee any particular order for these imports. ## [How to manage different stages of deferred loading](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/guide/defer#triggers) occurs or the `when` condition is met. By default, a @defer block is triggered when the browser state becomes [idle](https://angular.dev/guide/defer#idle). @defer { <large-component />} ### [Show placeholder content with `@placeholder`](https://angular.dev/#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](https://angular.dev/guide/templates/variables#template-reference-variables) to function. See the [Triggers](https://angular.dev/guide/defer#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`](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/#on) `on` specifies a condition for when the `@defer` block is triggered. The available triggers are as follows: | Trigger | Description | | --- | --- | | [`idle`](https://angular.dev/#idle) | Triggers when the browser is idle. | | [`viewport`](https://angular.dev/#viewport) | Triggers when specified content enters the viewport | | [`interaction`](https://angular.dev/#interaction) | Triggers when the user interacts with specified element | | [`hover`](https://angular.dev/#hover) | Triggers when the mouse hovers over specified area | | [`immediate`](https://angular.dev/#immediate) | Triggers immediately after non-deferred content has finished rendering | | [`timer`](https://angular.dev/#timer) | Triggers after a specific duration | #### [`idle`](https://angular.dev/#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`](https://angular.dev/#viewport) The `viewport` trigger loads the deferred content when the specified content enters the viewport using the [Intersection Observer API](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API). Observed content may be `@placeholder` content or an explicit element reference. By default, the `@defer` watches for the placeholder entering the viewport. Placeholders used this way must have a single root element. @defer (on viewport) { <large-cmp />} @placeholder { <div>Large component placeholder</div>} Alternatively, you can specify a [template reference variable](https://angular.dev/guide/templates/variables) in the same template as the `@defer` block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger. <div #greeting>Hello!</div>@defer (on viewport(greeting)) { <greetings-cmp />} #### [`interaction`](https://angular.dev/#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](https://angular.dev/guide/templates/variables) in the same template as the `@defer` block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger. <div #greeting>Hello!</div>@defer (on interaction(greeting)) { <greetings-cmp />} #### [`hover`](https://angular.dev/#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](https://angular.dev/guide/templates/variables) in the same template as the `@defer` block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger. <div #greeting>Hello!</div>@defer (on hover(greeting)) { <greetings-cmp />} #### [`immediate`](https://angular.dev/#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`](https://angular.dev/#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`](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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`?](https://angular.dev/#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. ## [How does `@defer` work with server-side rendering (SSR) and static-site generation (SSG)?](https://angular.dev/#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](https://angular.dev/guide/incremental-hydration) and configure `hydrate` triggers for the necessary blocks. ## [Best practices for deferring views](https://angular.dev/#best-practices-for-deferring-views) ### [Avoid cascading loads with nested `@defer` blocks](https://angular.dev/#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](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/templates/expression-syntax Angular expressions are based on JavaScript, but differ in some key ways. This guide walks through the similarities and differences between Angular expressions and standard JavaScript. ## [Value literals](https://angular.dev/#value-literals) Angular supports a subset of [literal values](https://developer.mozilla.org/en-US/docs/Glossary/Literal) from JavaScript. ### [Supported value literals](https://angular.dev/#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](https://angular.dev/#unsupported-literals) | Literal type | Example value | | --- | --- | | RegExp | `/\d+/` | | Tagged template string | `` tag`Hello ${name}` `` | ## [Globals](https://angular.dev/#globals) Angular expressions support the following [globals](https://developer.mozilla.org/en-US/docs/Glossary/Global_object): * [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) * [$any](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any) No other JavaScript globals are supported. Common JavaScript globals include `Number`, `Boolean`, `NaN`, `Infinity`, `parseInt`, and more. ## [Local variables](https://angular.dev/#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?](https://angular.dev/#what-operators-are-supported) ### [Supported operators](https://angular.dev/#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` | | Parenthesis | `9 * (8 + 4)` | | Conditional (Ternary) | `a > b ? true : false` | | And (Logical) | `&&` | | Or (Logical) | `||` | | Not (Logical) | `!` | | Nullish Coalescing | `const foo = null ?? 'default'` | | Comparison Operators | `<`, `<=`, `>`, `>=`, `==`, `===`, `!==` | | Unary Negation | `const y = -x` | | Unary Plus | `const x = +y` | | Property Accessor | `person['name'] = 'Mirabel'` | Angular expressions additionally also support the following non-standard operators: | Operator | Example(s) | | --- | --- | | [Pipe](https://angular.dev/guide/templates/pipes) | `{{ 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](https://angular.dev/#unsupported-operators) | Operator | Example(s) | | --- | --- | | All bitwise operators | `&`, `&=`, `~`, `|=`, `^=`, etc. | | Assignment operators | `=` | | Object destructuring | `const { name } = person` | | Array destructuring | `const [firstItem] = items` | | Comma operator | `x = (x++, x)` | | typeof | `typeof 42` | | void | `void 1` | | in | `'model' in car` | | instanceof | `car instanceof Automobile` | | new | `new Car()` | ## [Lexical context for expressions](https://angular.dev/#lexical-context-for-expressions) Angular expressions are evaluated within the context of the component class as well as any relevant [template variables](https://angular.dev/guide/templates/variables), locals, and globals. When referring to class members, `this` is always implied. ## [Declarations](https://angular.dev/#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 handlers are **statements** rather than expressions. While they support all of the same syntax as Angular expressions, the are two key differences: 1. Statements **do support** assignment operators (but not destructing assignments) 2. Statements **do not support** pipes --- ## Page: https://angular.dev/guide/templates/whitespace By default, Angular templates do not preserve whitespace that the framework considers unnecessary. This commonly occurs in two situations: whitespace between elements, and collapsible whitespace inside of text. ## [Whitespace between elements](https://angular.dev/#whitespace-between-elements) Most developers prefer to format their templates with newlines and indentation to make the template readable: <section> <h3>User profile</p> <label> User name <input> </label></section> This template contains whitespace between all of the elements. The following snippet shows the same HTML with each whitespace character replaced with the hash (`#`) character to highlight how much whitespace is present: <!-- Total Whitespace: 20 --><section>###<h3>User profile</p>###<label>#####User name#####<input>###</label>#</section> Preserving the whitespace as written in the template would result in many unnecessary [text nodes](https://developer.mozilla.org/en-US/docs/Web/API/Text) and increase page rendering overhead. By ignoring this whitespace between elements, Angular performs less work when rendering the template on the page, improving overall performance. ## [Collapsible whitespace inside text](https://angular.dev/#collapsible-whitespace-inside-text) When your web browser renders HTML on a page, it collapses multiple consecutive whitespace characters to a single character: <!-- What it looks like in the template --><p>Hello world</p> In this example, the browser displays only a single space between "Hello" and "world". <!-- What shows up in the browser --><p>Hello world</p> See [How whitespace is handled by HTML, CSS, and in the DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace) for more context on how this works. Angular avoids sending these unnecessary whitespace characters to the browser in the first place by collapsing them to a single character when it compiles the template. ## [Preserving whitespace](https://angular.dev/#preserving-whitespace) You can tell Angular to preserve whitespace in a template by specifying `preserveWhitespaces: true` in the `@Component` decorator for a template. @Component({ /* ... */, preserveWhitespaces: true, template: ` <p>Hello world</p> `}) Avoid setting this option unless absolutely necessary. Preserving whitespace can cause Angular to produce significantly more nodes while rendering, slowing down your application. You can additionally use a special HTML entity unique to Angular, `&ngsp;`. This entity produces a single space character that's preserved in the compiled output. --- ## Page: https://angular.dev/guide/templates/guide/components ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/guide/directives ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/AsyncPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/CurrencyPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/DatePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/DecimalPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/I18nPluralPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/I18nSelectPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/JsonPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/KeyValuePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/LowerCasePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/PercentPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/SlicePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/TitleCasePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/api/common/UpperCasePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/templates/templates/bindings ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/directives Use Angular's built-in directives to manage forms, lists, styles, and what users see. The different types of Angular directives are as follows: | Directive Types | Details | | --- | --- | | [Components](https://angular.dev/guide/components) | Used with a template. This type of directive is the most common directive type. | | [Attribute directives](https://angular.dev/#built-in-attribute-directives) | Change the appearance or behavior of an element, component, or another directive. | | [Structural directives](https://angular.dev/#built-in-structural-directives) | Change the DOM layout by adding and removing DOM elements. | This guide covers built-in [attribute directives](https://angular.dev/#built-in-attribute-directives) and [structural directives](https://angular.dev/#built-in-structural-directives). ## [Built-in attribute directives](https://angular.dev/#built-in-attribute-directives) Attribute directives listen to and modify the behavior of other HTML elements, attributes, properties, and components. The most common attribute directives are as follows: | Common directives | Details | | --- | --- | | [`NgClass`](https://angular.dev/#adding-and-removing-classes-with-ngclass) | Adds and removes a set of CSS classes. | | [`NgStyle`](https://angular.dev/#setting-inline-styles-with-ngstyle) | Adds and removes a set of HTML styles. | | [`NgModel`](https://angular.dev/#displaying-and-updating-properties-with-ngmodel) | Adds two-way data binding to an HTML form element. | **HELPFUL:** Built-in directives use only public APIs. They do not have special access to any private APIs that other directives can't access. ## [Adding and removing classes with `NgClass`](https://angular.dev/#adding-and-removing-classes-with-ngclass) Add or remove multiple CSS classes simultaneously with `ngClass`. **HELPFUL:** To add or remove a _single_ class, use [class binding](https://angular.dev/guide/templates/class-binding) rather than `NgClass`. ### [Import `NgClass` in the component](https://angular.dev/#import-ngclass-in-the-component) To use `NgClass`, add it to the component's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} ### [Using `NgClass` with an expression](https://angular.dev/#using-ngclass-with-an-expression) On the element you'd like to style, add `[ngClass]` and set it equal to an expression. In this case, `isSpecial` is a boolean set to `true` in `app.component.ts`. Because `isSpecial` is true, `ngClass` applies the class of `special` to the `<div>`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> ### [Using `NgClass` with a method](https://angular.dev/#using-ngclass-with-a-method) 1. To use `NgClass` with a method, add the method to the component class. In the following example, `setCurrentClasses()` sets the property `currentClasses` with an object that adds or removes three classes based on the `true` or `false` state of three other component properties. Each key of the object is a CSS class name. If a key is `true`, `ngClass` adds the class. If a key is `false`, `ngClass` removes the class. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} 2. In the template, add the `ngClass` property binding to `currentClasses` to set the element's classes: <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> For this use case, Angular applies the classes on initialization and in case of changes caused by reassigning the `currentClasses` object. The full example calls `setCurrentClasses()` initially with `ngOnInit()` when the user clicks on the `Refresh currentClasses` button. These steps are not necessary to implement `ngClass`. ## [Setting inline styles with `NgStyle`](https://angular.dev/#setting-inline-styles-with-ngstyle) **HELPFUL:** To add or remove a _single_ style, use [style bindings](https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings) rather than `NgStyle`. ### [Import `NgStyle` in the component](https://angular.dev/#import-ngstyle-in-the-component) To use `NgStyle`, add it to the component's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} Use `NgStyle` to set multiple inline styles simultaneously, based on the state of the component. 1. To use `NgStyle`, add a method to the component class. In the following example, `setCurrentStyles()` sets the property `currentStyles` with an object that defines three styles, based on the state of three other component properties. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} 2. To set the element's styles, add an `ngStyle` property binding to `currentStyles`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> For this use case, Angular applies the styles upon initialization and in case of changes. To do this, the full example calls `setCurrentStyles()` initially with `ngOnInit()` and when the dependent properties change through a button click. However, these steps are not necessary to implement `ngStyle` on its own. ## [Displaying and updating properties with `ngModel`](https://angular.dev/#displaying-and-updating-properties-with-ngmodel) Use the `NgModel` directive to display a data property and update that property when the user makes changes. 1. Import `FormsModule` and add it to the AppComponent's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} 1. Add an `[(ngModel)]` binding on an HTML `<form>` element and set it equal to the property, here `name`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> This `[(ngModel)]` syntax can only set a data-bound property. To customize your configuration, write the expanded form, which separates the property and event binding. Use [property binding](https://angular.dev/guide/templates/property-binding) to set the property and [event binding](https://angular.dev/guide/templates/event-listeners) to respond to changes. The following example changes the `<input>` value to uppercase: <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> Here are all variations in action, including the uppercase version:  ### [`NgModel` and value accessors](https://angular.dev/#ngmodel-and-value-accessors) The `NgModel` directive works for an element supported by a [ControlValueAccessor](https://angular.dev/api/forms/ControlValueAccessor). Angular provides _value accessors_ for all of the basic HTML form elements. For more information, see [Forms](https://angular.dev/guide/forms). To apply `[(ngModel)]` to a non-form built-in element or a third-party custom component, you have to write a value accessor. For more information, see the API documentation on [DefaultValueAccessor](https://angular.dev/api/forms/DefaultValueAccessor). **HELPFUL:** When you write an Angular component, you don't need a value accessor or `NgModel` if you name the value and event properties according to Angular's [two-way binding syntax](https://angular.dev/guide/templates/two-way-binding#how-two-way-binding-works). ## [Built-in structural directives](https://angular.dev/#built-in-structural-directives) Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, and manipulating the host elements to which they are attached. This section introduces the most common built-in structural directives: | Common built-in structural directives | Details | | --- | --- | | [`NgIf`](https://angular.dev/#adding-or-removing-an-element-with-ngif) | Conditionally creates or disposes of subviews from the template. | | [`NgFor`](https://angular.dev/#listing-items-with-ngfor) | Repeat a node for each item in a list. | | [`NgSwitch`](https://angular.dev/#switching-cases-with-ngswitch) | A set of directives that switch among alternative views. | For more information, see [Structural Directives](https://angular.dev/guide/directives/structural-directives). ## [Adding or removing an element with `NgIf`](https://angular.dev/#adding-or-removing-an-element-with-ngif) Add or remove an element by applying an `NgIf` directive to a host element. When `NgIf` is `false`, Angular removes an element and its descendants from the DOM. Angular then disposes of their components, which frees up memory and resources. ### [Import `NgIf` in the component](https://angular.dev/#import-ngif-in-the-component) To use `NgIf`, add it to the component's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} ### [Using `NgIf`](https://angular.dev/#using-ngif) To add or remove an element, bind `*ngIf` to a condition expression such as `isActive` in the following example. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> When the `isActive` expression returns a truthy value, `NgIf` adds the `ItemDetailComponent` to the DOM. When the expression is falsy, `NgIf` removes the `ItemDetailComponent` from the DOM and disposes of the component and all of its subcomponents. For more information on `NgIf` and `NgIfElse`, see the [NgIf API documentation](https://angular.dev/api/common/NgIf). ### [Guarding against `null`](https://angular.dev/#guarding-against-null) By default, `NgIf` prevents display of an element bound to a null value. To use `NgIf` to guard a `<div>`, add `*ngIf="yourProperty"` to the `<div>`. In the following example, the `currentCustomer` name appears because there is a `currentCustomer`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> However, if the property is `null`, Angular does not display the `<div>`. In this example, Angular does not display the `nullCustomer` because it is `null`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> ## [Listing items with `NgFor`](https://angular.dev/#listing-items-with-ngfor) Use the `NgFor` directive to present a list of items. ### [Import `NgFor` in the component](https://angular.dev/#import-ngfor-in-the-component) To use `NgFor`, add it to the component's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} ### [Using `NgFor`](https://angular.dev/#using-ngfor) To use `NgFor`, you have to: 1. Define a block of HTML that determines how Angular renders a single item. 2. To list your items, assign the shorthand `let item of items` to `*ngFor`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> The string `"let item of items"` instructs Angular to do the following: * Store each item in the `items` array in the local `item` looping variable * Make each item available to the templated HTML for each iteration * Translate `"let item of items"` into an `<ng-template>` around the host element * Repeat the `<ng-template>` for each `item` in the list For more information see the [Structural directive shorthand](https://angular.dev/guide/directives/structural-directives#structural-directive-shorthand) section of [Structural directives](https://angular.dev/guide/directives/structural-directives). ### [Repeating a component view](https://angular.dev/#repeating-a-component-view) To repeat a component element, apply `*ngFor` to the selector. In the following example, the selector is `<app-item-detail>`. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> Reference a template input variable, such as `item`, in the following locations: * Within the `ngFor` host element * Within the host element descendants to access the item's properties The following example references `item` first in an interpolation and then passes in a binding to the `item` property of the `<app-item-detail>` component. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> For more information about template input variables, see [Structural directive shorthand](https://angular.dev/guide/directives/structural-directives#structural-directive-shorthand). ### [Getting the `index` of `*ngFor`](https://angular.dev/#getting-the-index-of-ngfor) Get the `index` of `*ngFor` in a template input variable and use it in the template. In the `*ngFor`, add a semicolon and `let i=index` to the shorthand. The following example gets the `index` in a variable named `i` and displays it with the item name. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> The index property of the `NgFor` directive context returns the zero-based index of the item in each iteration. Angular translates this instruction into an `<ng-template>` around the host element, then uses this template repeatedly to create a new set of elements and bindings for each `item` in the list. For more information about shorthand, see the [Structural Directives](https://angular.dev/guide/directives/structural-directives#structural-directive-shorthand) guide. ## [Repeating elements when a condition is true](https://angular.dev/#repeating-elements-when-a-condition-is-true) To repeat a block of HTML when a particular condition is true, put the `*ngIf` on a container element that wraps an `*ngFor` element. For more information see [one structural directive per element](https://angular.dev/guide/directives/structural-directives#one-structural-directive-per-element). ### [Tracking items with `*ngFor` `trackBy`](https://angular.dev/#tracking-items-with-ngfor-trackby) Reduce the number of calls your application makes to the server by tracking changes to an item list. With the `*ngFor` `trackBy` property, Angular can change and re-render only those items that have changed, rather than reloading the entire list of items. 1. Add a method to the component that returns the value `NgFor` should track. In this example, the value to track is the item's `id`. If the browser has already rendered `id`, Angular keeps track of it and doesn't re-query the server for the same `id`. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} 1. In the shorthand expression, set `trackBy` to the `trackByItems()` method. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> **Change ids** creates new items with new `item.id`s. In the following illustration of the `trackBy` effect, **Reset items** creates new items with the same `item.id`s. * With no `trackBy`, both buttons trigger complete DOM element replacement. * With `trackBy`, only changing the `id` triggers element replacement.  ## [Hosting a directive without a DOM element](https://angular.dev/#hosting-a-directive-without-a-dom-element) The Angular `<ng-container>` is a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM. Use `<ng-container>` when there's no single element to host the directive. Here's a conditional paragraph using `<ng-container>`. <h1>Structural Directives</h1><p>Conditional display of hero</p><blockquote><div *ngIf="hero" class="name">{{hero.name}}</div></blockquote><p>List of heroes</p><ul> <li *ngFor="let hero of heroes">{{hero.name}}</li></ul><hr><h2 id="ngIf">NgIf</h2><p *ngIf="true"> Expression is true and ngIf is true. This paragraph is in the DOM.</p><p *ngIf="false"> Expression is false and ngIf is false. This paragraph is not in the DOM.</p><p [style.display]="'block'"> Expression sets display to "block". This paragraph is visible.</p><p [style.display]="'none'"> Expression sets display to "none". This paragraph is hidden but still in the DOM.</p><h4>NgIf with template</h4><p><ng-template> element</p><ng-template [ngIf]="hero"> <div class="name">{{hero.name}}</div></ng-template><hr><h2 id="ng-container"><ng-container></h2><h4>*ngIf with a <ng-container></h4><button type="button" (click)="hero = hero ? null : heroes[0]">Toggle hero</button><p> I turned the corner <ng-container *ngIf="hero"> and saw {{hero.name}}. I waved </ng-container> and continued on my way.</p><p> I turned the corner <span *ngIf="hero"> and saw {{hero.name}}. I waved </span> and continued on my way.</p><p><em><select> with <span></em></p><div> Pick your favorite hero (<label for="show-sad"><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero"> <span *ngFor="let h of heroes"> <span *ngIf="showSad || h.emotion !== 'sad'"> <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> </span> </span></select><p><em><select> with <ng-container></em></p><div> Pick your favorite hero (<label for="showSad"><input id="showSad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero"> <ng-container *ngFor="let h of heroes"> <ng-container *ngIf="showSad || h.emotion !== 'sad'"> <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> </ng-container> </ng-container></select><br><br><hr><h2 id="ngFor">NgFor</h2><div class="box"><p class="code"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></p><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"> ({{i}}) {{hero.name}}</div><p class="code"><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/></p><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"> <div [class.odd]="odd"> ({{i}}) {{hero.name}} </div></ng-template></div><hr><h2 id="ngSwitch">NgSwitch</h2><div>Pick your favorite hero</div><p> <label for="hero-{{h}}" *ngFor="let h of heroes"> <input id="hero-{{h}}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}} </label> <label for="none-of-the-above"><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null">None of the above</label></p><h4>NgSwitch</h4><div [ngSwitch]="hero?.emotion"> <app-happy-hero *ngSwitchCase="'happy'" [hero]="hero!"></app-happy-hero> <app-sad-hero *ngSwitchCase="'sad'" [hero]="hero!"></app-sad-hero> <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero> <app-unknown-hero *ngSwitchDefault [hero]="hero!"></app-unknown-hero></div><h4>NgSwitch with <ng-template></h4><div [ngSwitch]="hero?.emotion"> <ng-template ngSwitchCase="happy"> <app-happy-hero [hero]="hero!"></app-happy-hero> </ng-template> <ng-template ngSwitchCase="sad"> <app-sad-hero [hero]="hero!"></app-sad-hero> </ng-template> <ng-template ngSwitchCase="confused"> <app-confused-hero [hero]="hero!"></app-confused-hero> </ng-template > <ng-template ngSwitchDefault> <app-unknown-hero [hero]="hero!"></app-unknown-hero> </ng-template></div><hr><h2 id="appUnless">UnlessDirective</h2><p> The condition is currently <span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>. <button type="button" (click)="condition = !condition" [ngClass] = "{ 'a': condition, 'b': !condition }" > Toggle condition to {{condition ? 'false' : 'true'}} </button></p><p *appUnless="condition" class="unless a"> (A) This paragraph is displayed because the condition is false.</p><p *appUnless="!condition" class="unless b"> (B) Although the condition is true, this paragraph is displayed because appUnless is set to false.</p><h4>UnlessDirective with template</h4><p *appUnless="condition">Show this sentence unless the condition is true.</p><p *appUnless="condition" class="code unless"> (A) <p *appUnless="condition" class="code unless"></p><ng-template [appUnless]="condition"> <p class="code unless"> (A) <ng-template [appUnless]="condition"> </p></ng-template><hr><h2 id="appIfLoaded">IfLoadedDirective</h2><app-hero></app-hero><hr><h2 id="appTrigonometry">TrigonometryDirective</h2><ul *appTrigonometry="30; sin as s; cos as c; tan as t"> <li>sin(30°): {{ s }}</li> <li>cos(30°): {{ c }}</li> <li>tan(30°): {{ t }}</li></ul>  1. Import the `ngModel` directive from `FormsModule`. 2. Add `FormsModule` to the imports section of the relevant Angular module. 3. To conditionally exclude an `<option>`, wrap the `<option>` in an `<ng-container>`. <h1>Structural Directives</h1><p>Conditional display of hero</p><blockquote><div *ngIf="hero" class="name">{{hero.name}}</div></blockquote><p>List of heroes</p><ul> <li *ngFor="let hero of heroes">{{hero.name}}</li></ul><hr><h2 id="ngIf">NgIf</h2><p *ngIf="true"> Expression is true and ngIf is true. This paragraph is in the DOM.</p><p *ngIf="false"> Expression is false and ngIf is false. This paragraph is not in the DOM.</p><p [style.display]="'block'"> Expression sets display to "block". This paragraph is visible.</p><p [style.display]="'none'"> Expression sets display to "none". This paragraph is hidden but still in the DOM.</p><h4>NgIf with template</h4><p><ng-template> element</p><ng-template [ngIf]="hero"> <div class="name">{{hero.name}}</div></ng-template><hr><h2 id="ng-container"><ng-container></h2><h4>*ngIf with a <ng-container></h4><button type="button" (click)="hero = hero ? null : heroes[0]">Toggle hero</button><p> I turned the corner <ng-container *ngIf="hero"> and saw {{hero.name}}. I waved </ng-container> and continued on my way.</p><p> I turned the corner <span *ngIf="hero"> and saw {{hero.name}}. I waved </span> and continued on my way.</p><p><em><select> with <span></em></p><div> Pick your favorite hero (<label for="show-sad"><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero"> <span *ngFor="let h of heroes"> <span *ngIf="showSad || h.emotion !== 'sad'"> <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> </span> </span></select><p><em><select> with <ng-container></em></p><div> Pick your favorite hero (<label for="showSad"><input id="showSad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)</div><select [(ngModel)]="hero"> <ng-container *ngFor="let h of heroes"> <ng-container *ngIf="showSad || h.emotion !== 'sad'"> <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> </ng-container> </ng-container></select><br><br><hr><h2 id="ngFor">NgFor</h2><div class="box"><p class="code"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></p><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"> ({{i}}) {{hero.name}}</div><p class="code"><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/></p><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"> <div [class.odd]="odd"> ({{i}}) {{hero.name}} </div></ng-template></div><hr><h2 id="ngSwitch">NgSwitch</h2><div>Pick your favorite hero</div><p> <label for="hero-{{h}}" *ngFor="let h of heroes"> <input id="hero-{{h}}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}} </label> <label for="none-of-the-above"><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null">None of the above</label></p><h4>NgSwitch</h4><div [ngSwitch]="hero?.emotion"> <app-happy-hero *ngSwitchCase="'happy'" [hero]="hero!"></app-happy-hero> <app-sad-hero *ngSwitchCase="'sad'" [hero]="hero!"></app-sad-hero> <app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero> <app-unknown-hero *ngSwitchDefault [hero]="hero!"></app-unknown-hero></div><h4>NgSwitch with <ng-template></h4><div [ngSwitch]="hero?.emotion"> <ng-template ngSwitchCase="happy"> <app-happy-hero [hero]="hero!"></app-happy-hero> </ng-template> <ng-template ngSwitchCase="sad"> <app-sad-hero [hero]="hero!"></app-sad-hero> </ng-template> <ng-template ngSwitchCase="confused"> <app-confused-hero [hero]="hero!"></app-confused-hero> </ng-template > <ng-template ngSwitchDefault> <app-unknown-hero [hero]="hero!"></app-unknown-hero> </ng-template></div><hr><h2 id="appUnless">UnlessDirective</h2><p> The condition is currently <span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>. <button type="button" (click)="condition = !condition" [ngClass] = "{ 'a': condition, 'b': !condition }" > Toggle condition to {{condition ? 'false' : 'true'}} </button></p><p *appUnless="condition" class="unless a"> (A) This paragraph is displayed because the condition is false.</p><p *appUnless="!condition" class="unless b"> (B) Although the condition is true, this paragraph is displayed because appUnless is set to false.</p><h4>UnlessDirective with template</h4><p *appUnless="condition">Show this sentence unless the condition is true.</p><p *appUnless="condition" class="code unless"> (A) <p *appUnless="condition" class="code unless"></p><ng-template [appUnless]="condition"> <p class="code unless"> (A) <ng-template [appUnless]="condition"> </p></ng-template><hr><h2 id="appIfLoaded">IfLoadedDirective</h2><app-hero></app-hero><hr><h2 id="appTrigonometry">TrigonometryDirective</h2><ul *appTrigonometry="30; sin as s; cos as c; tan as t"> <li>sin(30°): {{ s }}</li> <li>cos(30°): {{ c }}</li> <li>tan(30°): {{ t }}</li></ul>  ## [Switching cases with `NgSwitch`](https://angular.dev/#switching-cases-with-ngswitch) Like the JavaScript `switch` statement, `NgSwitch` displays one element from among several possible elements, based on a switch condition. Angular puts only the selected element into the DOM. `NgSwitch` is a set of three directives: | `NgSwitch` directives | Details | | --- | --- | | `NgSwitch` | An attribute directive that changes the behavior of its companion directives. | | `NgSwitchCase` | Structural directive that adds its element to the DOM when its bound value equals the switch value and removes its bound value when it doesn't equal the switch value. | | `NgSwitchDefault` | Structural directive that adds its element to the DOM when there is no selected `NgSwitchCase`. | To use the directives, add the `NgSwitch`, `NgSwitchCase` and `NgSwitchDefault` to the component's `imports` list. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} ### [Using `NgSwitch`](https://angular.dev/#using-ngswitch) 1. On an element, such as a `<div>`, add `[ngSwitch]` bound to an expression that returns the switch value, such as `feature`. Though the `feature` value in this example is a string, the switch value can be of any type. 2. Bind to `*ngSwitchCase` and `*ngSwitchDefault` on the elements for the cases. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> 1. In the parent component, define `currentItem`, to use it in the `[ngSwitch]` expression. import {Component, OnInit} from '@angular/core';import {JsonPipe} from '@angular/common';import {NgIf} from '@angular/common';import {NgFor} from '@angular/common';import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';import {NgStyle} from '@angular/common';import {NgClass} from '@angular/common';import {FormsModule} from '@angular/forms';import {Item} from './item';import {ItemDetailComponent} from './item-detail/item-detail.component';import {ItemSwitchComponents} from './item-switch.component';import {StoutItemComponent} from './item-switch.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ NgIf, // <-- import into the component NgFor, // <-- import into the component NgStyle, // <-- import into the component NgSwitch, // <-- import into the component NgSwitchCase, NgSwitchDefault, NgClass, // <-- import into the component FormsModule, // <--- import into the component JsonPipe, ItemDetailComponent, ItemSwitchComponents, StoutItemComponent, ],})export class AppComponent implements OnInit { canSave = true; isSpecial = true; isUnchanged = true; isActive = true; nullCustomer: string | null = null; currentCustomer = { name: 'Laura', }; item!: Item; // defined to demonstrate template context precedence items: Item[] = []; currentItem!: Item; // trackBy change counting itemsNoTrackByCount = 0; itemsWithTrackByCount = 0; itemsWithTrackByCountReset = 0; itemIdIncrement = 1; currentClasses: Record<string, boolean> = {}; currentStyles: Record<string, string> = {}; ngOnInit() { this.resetItems(); this.setCurrentClasses(); this.setCurrentStyles(); this.itemsNoTrackByCount = 0; } setUppercaseName(name: string) { this.currentItem.name = name.toUpperCase(); } setCurrentClasses() { // CSS classes: added/removed per current state of component properties this.currentClasses = { saveable: this.canSave, modified: !this.isUnchanged, special: this.isSpecial, }; } setCurrentStyles() { // CSS styles: set per current state of component properties this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px', }; } isActiveToggle() { this.isActive = !this.isActive; } giveNullCustomerValue() { this.nullCustomer = 'Kelly'; } resetItems() { this.items = Item.items.map((item) => item.clone()); this.currentItem = this.items[0]; this.item = this.currentItem; } resetList() { this.resetItems(); this.itemsWithTrackByCountReset = 0; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; } changeIds() { this.items.forEach((i) => (i.id += 1 * this.itemIdIncrement)); this.itemsWithTrackByCountReset = -1; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsWithTrackByCount = ++this.itemsWithTrackByCount; } clearTrackByCounts() { this.resetItems(); this.itemsNoTrackByCount = 0; this.itemsWithTrackByCount = 0; this.itemIdIncrement = 1; } trackByItems(index: number, item: Item): number { return item.id; } trackById(index: number, item: any): number { return item.id; } getValue(event: Event): string { return (event.target as HTMLInputElement).value; }} 1. In each child component, add an `item` [input property](https://angular.dev/guide/components/inputs) which is bound to the `currentItem` of the parent component. The following two snippets show the parent component and one of the child components. The other child components are identical to `StoutItemComponent`. import {Component, Input} from '@angular/core';import {Item} from './item';@Component({ selector: 'app-stout-item', template: "I'm a little {{item.name}}, short and stout!",})export class StoutItemComponent { @Input() item!: Item;}@Component({ selector: 'app-best-item', template: 'This is the brightest {{item.name}} in town.',})export class BestItemComponent { @Input() item!: Item;}@Component({ selector: 'app-device-item', template: 'Which is the slimmest {{item.name}}?',})export class DeviceItemComponent { @Input() item!: Item;}@Component({ selector: 'app-lost-item', template: 'Has anyone seen my {{item.name}}?',})export class LostItemComponent { @Input() item!: Item;}@Component({ selector: 'app-unknown-item', template: '{{message}}',})export class UnknownItemComponent { @Input() item!: Item; get message() { return this.item && this.item.name ? `${this.item.name} is strange and mysterious.` : 'A mystery wrapped in a fishbowl.'; }}export const ItemSwitchComponents = [ StoutItemComponent, BestItemComponent, DeviceItemComponent, LostItemComponent, UnknownItemComponent,];  Switch directives also work with built-in HTML elements and web components. For example, you could replace the `<app-best-item>` switch case with a `<div>` as follows. <h1>Built-in Directives</h1><h2>Built-in attribute directives</h2><h3 id="ngModel">NgModel (two-way) Binding</h3><fieldset><h4>NgModel examples</h4> <p>Current item name: {{ currentItem.name }}</p> <p> <label for="without">without NgModel:</label> <input [value]="currentItem.name" (input)="currentItem.name=getValue($event)" id="without"> </p> <p> <label for="example-ngModel">[(ngModel)]:</label> <input [(ngModel)]="currentItem.name" id="example-ngModel"> </p> <p> <label for="example-change">(ngModelChange)="...name=$event":</label> <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change"> </p> <p> <label for="example-uppercase">(ngModelChange)="setUppercaseName($event)" <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase"> </label> </p></fieldset><hr><h2 id="ngClass">NgClass Binding</h2><p>currentClasses is {{ currentClasses | json }}</p><div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div><ul> <li> <label for="saveable">saveable</label> <input type="checkbox" [(ngModel)]="canSave" id="saveable"> </li> <li> <label for="modified">modified:</label> <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li> <li> <label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label></li></ul><button type="button" (click)="setCurrentClasses()">Refresh currentClasses</button><div [ngClass]="currentClasses"> This div should be {{ canSave ? "": "not"}} saveable, {{ isUnchanged ? "unchanged" : "modified" }} and {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div><br><br><!-- toggle the "special" class on/off with a property --><div [ngClass]="isSpecial ? 'special' : ''">This div is special</div><div class="helpful study course">Helpful study course</div><div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div><!-- NgStyle binding --><hr><h3>NgStyle Binding</h3><div [style.font-size]="isSpecial ? 'x-large' : 'smaller'"> This div is x-large or smaller.</div><h4>[ngStyle] binding to currentStyles - CSS property names</h4><p>currentStyles is {{ currentStyles | json }}</p><div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px).</div><br><label for="canSave">italic: <input id="canSave" type="checkbox" [(ngModel)]="canSave"></label> |<label for="isUnchanged">normal: <input id="isUnchanged" type="checkbox" [(ngModel)]="isUnchanged"></label> |<label for="isSpecial">xlarge: <input id="isSpecial" type="checkbox" [(ngModel)]="isSpecial"></label><button type="button" (click)="setCurrentStyles()">Refresh currentStyles</button><br><br><div [ngStyle]="currentStyles"> This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div><hr><h2>Built-in structural directives</h2><h3 id="ngIf">NgIf Binding</h3><div> <p>If isActive is true, app-item-detail will render: </p> <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail> <button type="button" (click)="isActiveToggle()">Toggle app-item-detail</button></div><p>If currentCustomer isn't null, say hello to Laura:</p><div *ngIf="currentCustomer">Hello, {{ currentCustomer.name }}</div><p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p><div *ngIf="nullCustomer">Hello, <span>{{ nullCustomer }}</span></div><button type="button" (click)="giveNullCustomerValue()">Give nullCustomer a value</button><h4>NgIf binding with template (no *)</h4><ng-template [ngIf]="currentItem">Add {{ currentItem.name }} with template</ng-template><hr><h4>Show/hide vs. NgIf</h4><!-- isSpecial is true --><div [class.hidden]="!isSpecial">Show with class</div><div [class.hidden]="isSpecial">Hide with class</div><p>ItemDetail is in the DOM but hidden</p><app-item-detail [class.hidden]="isSpecial"></app-item-detail><div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div><div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div><hr><h2 id="ngFor">NgFor Binding</h2><div class="box"> <div *ngFor="let item of items">{{ item.name }}</div></div><p>*ngFor with ItemDetailComponent element</p><div class="box"> <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail></div><h4 id="ngFor-index">*ngFor with index</h4><p>with <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; let i=index">{{ i + 1 }} - {{ item.name }}</div></div><p>with <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, let i=index">{{ i + 1 }} - {{ item.name }}</div></div><h4 id="ngFor-trackBy">*ngFor trackBy</h4><button type="button" (click)="resetList()">Reset items</button><button type="button" (click)="changeIds()">Change ids</button><button type="button" (click)="clearTrackByCounts()">Clear counts</button><p><em>without</em> trackBy</p><div class="box"> <div #noTrackBy *ngFor="let item of items">({{ item.id }}) {{ item.name }}</div> <div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" > Item DOM elements change #{{ itemsNoTrackByCount }} without trackBy </div></div><p>with trackBy</p><div class="box"> <div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div> <div id="withTrackByCnt" *ngIf="itemsWithTrackByCount"> Item DOM elements change #{{ itemsWithTrackByCount }} with trackBy </div></div><br><br><br><p>with trackBy and <em>semi-colon</em> separator</p><div class="box"> <div *ngFor="let item of items; trackBy: trackByItems"> ({{ item.id }}) {{ item.name }} </div></div><p>with trackBy and <em>comma</em> separator</p><div class="box"> <div *ngFor="let item of items, trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with trackBy and <em>space</em> separator</p><div class="box"> <div *ngFor="let item of items trackBy: trackByItems">({{ item.id }}) {{ item.name }}</div></div><p>with <em>generic</em> trackById function</p><div class="box"> <div *ngFor="let item of items, trackBy: trackById">({{ item.id }}) {{ item.name }}</div></div><hr><h2>NgSwitch Binding</h2><p>Pick your favorite item</p><div> <label for="item-{{i}}" *ngFor="let i of items"> <div><input id="item-{{i}}"type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{ i.name }} </div> </label></div><div [ngSwitch]="currentItem.feature"> <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item> <app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item> <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item> <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item> <div *ngSwitchCase="'bright'">Are you as bright as {{ currentItem.name }}?</div> <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item></div> ## [What's next](https://angular.dev/#whats-next) [Attribute Directives](https://angular.dev/guide/directives/attribute-directives) [Structural Directives](https://angular.dev/guide/directives/structural-directives) [Directive composition API](https://angular.dev/guide/directives/directive-composition-api) --- ## Page: https://angular.dev/guide/directives/attribute-directives Change the appearance or behavior of DOM elements and Angular components with attribute directives. ## [Building an attribute directive](https://angular.dev/#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`](https://angular.dev/tools/cli/schematics). ng generate directive highlight The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`. import {Directive} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective {} The `@Directive()` decorator's configuration property specifies the directive's CSS attribute selector, `[appHighlight]`. 2. Import `ElementRef` from `@angular/core`. `ElementRef` grants direct access to the host DOM element through its `nativeElement` property. 3. Add `ElementRef` in the directive's `constructor()` to [inject](https://angular.dev/guide/di) a reference to the host DOM element, the element to which you apply `appHighlight`. 4. Add logic to the `HighlightDirective` class that sets the background to yellow. import {Directive, ElementRef, inject} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); constructor() { this.el.nativeElement.style.backgroundColor = 'yellow'; }} **HELPFUL:** Directives _do not_ support namespaces. <p app:Highlight>This is invalid</p> ## [Applying an attribute directive](https://angular.dev/#applying-an-attribute-directive) 1. To use the `HighlightDirective`, add a `<p>` element to the HTML template with the directive as an attribute. <h1>My First Attribute Directive</h1><p appHighlight>Highlight me!</p><p appHighlight="yellow">Highlighted in yellow</p><p [appHighlight]="'orange'">Highlighted in orange</p><p [appHighlight]="color">Highlighted with parent component's color</p> 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](https://angular.dev/#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'. import {Directive, ElementRef, HostListener, inject} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 2. Add two event handlers that respond when the mouse enters or leaves, each with the `@HostListener()` decorator. import {Directive, ElementRef, HostListener, inject} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 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: import {Directive, ElementRef, HostListener, inject} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} The background color appears when the pointer hovers over the paragraph element and disappears as the pointer moves out.  ## [Passing values into an attribute directive](https://angular.dev/#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`. import {Directive, ElementRef, HostListener, inject, Input} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @Input() appHighlight = ''; @HostListener('mouseenter') onMouseEnter() { this.highlight(this.appHighlight || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 2. Add an `appHighlight` `@Input()` property. import {Directive, ElementRef, HostListener, inject, Input} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @Input() appHighlight = ''; @HostListener('mouseenter') onMouseEnter() { this.highlight(this.appHighlight || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} The `@Input()` decorator adds metadata to the class that makes the directive's `appHighlight` property available for binding. 3. In `app.component.ts`, add a `color` property to the `AppComponent`. import {Component} from '@angular/core';import {HighlightDirective} from './highlight.directive';@Component({ selector: 'app-root', templateUrl: './app.component.1.html', imports: [HighlightDirective],})export class AppComponent { color = 'yellow';} 4. To simultaneously apply the directive and the color, use property binding with the `appHighlight` directive selector, setting it equal to `color`. <h1>My First Attribute Directive</h1><h2>Pick a highlight color</h2><div> <input type="radio" name="colors" (click)="color='lightgreen'">Green <input type="radio" name="colors" (click)="color='yellow'">Yellow <input type="radio" name="colors" (click)="color='cyan'">Cyan</div><p [appHighlight]="color">Highlight me!</p><p [appHighlight]="color" defaultColor="violet"> Highlight me too!</p><hr><h2>Mouse over the following lines to see fixed highlights</h2><p [appHighlight]="'yellow'">Highlighted in yellow</p><p appHighlight="orange">Highlighted in orange</p><hr><h2>ngNonBindable</h2><p>Use ngNonBindable to stop evaluation.</p><p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p><h3>ngNonBindable with a directive</h3><div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.</div> 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](https://angular.dev/#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: <h1>My First Attribute Directive</h1><h2>Pick a highlight color</h2><div> <input type="radio" name="colors" (click)="color='lightgreen'">Green <input type="radio" name="colors" (click)="color='yellow'">Yellow <input type="radio" name="colors" (click)="color='cyan'">Cyan</div><p [appHighlight]="color">Highlight me!</p><p [appHighlight]="color" defaultColor="violet"> Highlight me too!</p><hr><h2>Mouse over the following lines to see fixed highlights</h2><p [appHighlight]="'yellow'">Highlighted in yellow</p><p appHighlight="orange">Highlighted in orange</p><hr><h2>ngNonBindable</h2><p>Use ngNonBindable to stop evaluation.</p><p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p><h3>ngNonBindable with a directive</h3><div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.</div> 2. Revise the `AppComponent.color` so that it has no initial value. import {Component} from '@angular/core';import {HighlightDirective} from './highlight.directive';@Component({ selector: 'app-root', templateUrl: './app.component.html', imports: [HighlightDirective],})export class AppComponent { color = '';} 3. 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`. import {Directive, ElementRef, HostListener, inject, Input} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @Input() appHighlight = ''; @HostListener('mouseenter') onMouseEnter() { this.highlight(this.appHighlight || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 4. Serve your application to verify that the user can choose the color with the radio buttons.  ## [Binding to a second property](https://angular.dev/#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`. import {Directive, ElementRef, HostListener, inject, Input} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @Input() defaultColor = ''; @Input() appHighlight = ''; @HostListener('mouseenter') onMouseEnter() { this.highlight(this.appHighlight || this.defaultColor || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 2. 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`. import {Directive, ElementRef, HostListener, inject, Input} from '@angular/core';@Directive({ selector: '[appHighlight]',})export class HighlightDirective { private el = inject(ElementRef); @Input() defaultColor = ''; @Input() appHighlight = ''; @HostListener('mouseenter') onMouseEnter() { this.highlight(this.appHighlight || this.defaultColor || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(''); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; }} 3. 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. <h1>My First Attribute Directive</h1><h2>Pick a highlight color</h2><div> <input type="radio" name="colors" (click)="color='lightgreen'">Green <input type="radio" name="colors" (click)="color='yellow'">Yellow <input type="radio" name="colors" (click)="color='cyan'">Cyan</div><p [appHighlight]="color">Highlight me!</p><p [appHighlight]="color" defaultColor="violet"> Highlight me too!</p><hr><h2>Mouse over the following lines to see fixed highlights</h2><p [appHighlight]="'yellow'">Highlighted in yellow</p><p appHighlight="orange">Highlighted in orange</p><hr><h2>ngNonBindable</h2><p>Use ngNonBindable to stop evaluation.</p><p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p><h3>ngNonBindable with a directive</h3><div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.</div> 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.  ## [Deactivating Angular processing with `NgNonBindable`](https://angular.dev/#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`. <h1>My First Attribute Directive</h1><h2>Pick a highlight color</h2><div> <input type="radio" name="colors" (click)="color='lightgreen'">Green <input type="radio" name="colors" (click)="color='yellow'">Yellow <input type="radio" name="colors" (click)="color='cyan'">Cyan</div><p [appHighlight]="color">Highlight me!</p><p [appHighlight]="color" defaultColor="violet"> Highlight me too!</p><hr><h2>Mouse over the following lines to see fixed highlights</h2><p [appHighlight]="'yellow'">Highlighted in yellow</p><p appHighlight="orange">Highlighted in orange</p><hr><h2>ngNonBindable</h2><p>Use ngNonBindable to stop evaluation.</p><p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p><h3>ngNonBindable with a directive</h3><div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.</div> 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 }}`. <h1>My First Attribute Directive</h1><h2>Pick a highlight color</h2><div> <input type="radio" name="colors" (click)="color='lightgreen'">Green <input type="radio" name="colors" (click)="color='yellow'">Yellow <input type="radio" name="colors" (click)="color='cyan'">Cyan</div><p [appHighlight]="color">Highlight me!</p><p [appHighlight]="color" defaultColor="violet"> Highlight me too!</p><hr><h2>Mouse over the following lines to see fixed highlights</h2><p [appHighlight]="'yellow'">Highlighted in yellow</p><p appHighlight="orange">Highlighted in orange</p><hr><h2>ngNonBindable</h2><p>Use ngNonBindable to stop evaluation.</p><p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p><h3>ngNonBindable with a directive</h3><div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.</div> If you apply `ngNonBindable` to a parent element, Angular disables interpolation and binding of any sort, such as property binding or event binding, for the element's children. --- ## Page: https://angular.dev/guide/directives/structural-directives Structural directives are directives applied to an `<ng-template>` element that conditionally or repeatedly render the content of that `<ng-template>`. ## [Example use case](https://angular.dev/#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](https://angular.dev/#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: <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](https://angular.dev/api/core/ng-template) documentation. ## [Structural directive shorthand](https://angular.dev/#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](https://angular.dev/#structural-directive-syntax-reference) is defined below, but in the above example, this transformation can be explained as follows: The first part of the `*select` expression is `let data`, which declares a template variable `data`. Since no assignment follows, the template variable is bound to the template context property `$implicit`. The second piece of syntax is a key-expression pair, `from source`. `from` is a binding key and `source` is a regular template expression. Binding keys are mapped to properties by transforming them to PascalCase and prepending the structural directive selector. The `from` key is mapped to `selectFrom`, which is then bound to the expression `source`. This is why many structural directives will have inputs that are all prefixed with the structural directive's selector. ## [One structural directive per element](https://angular.dev/#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](https://angular.dev/#creating-a-structural-directive) This section guides you through creating the `SelectDirective`. 1. ### [Generate the directive](https://angular.dev/#generate-the-directive) 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. 2. ### [Make the directive structural](https://angular.dev/#make-the-directive-structural) 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);} 3. ### [Add the 'selectFrom' input](https://angular.dev/#add-the-selectfrom-input) Add a `selectFrom` `@Input()` property. export class SelectDirective { // ... @Input({required: true}) selectFrom!: DataSource;} 4. ### [Add the business logic](https://angular.dev/#add-the-business-logic) 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](https://angular.dev/#typing-the-directives-context). ## [Structural directive syntax reference](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#shorthand-examples) The following table provides shorthand examples: | Shorthand | How Angular interprets the syntax | | --- | --- | | `*ngFor="let item of [1,2,3]"` | `<ng-template ngFor let-item [ngForOf]="[1, 2, 3]">` | | `*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i"` | `<ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index">` | | `*ngIf="exp"` | `<ng-template [ngIf]="exp">` | | `*ngIf="exp as value"` | `<ng-template [ngIf]="exp" let-value="ngIf">` | ## [Improving template type checking for custom directives](https://angular.dev/#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](https://angular.dev/tools/cli/template-typecheck "Template"). ### [Type narrowing with template guards](https://angular.dev/#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 { @Input() actor: 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 { @Input() condition!: any; 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](https://angular.dev/#typing-the-directives-context) If your structural directive provides a context to the instantiated template, you can properly type it inside the template by providing a static `ngTemplateContextGuard` type assertion function. This function can use the type of the directive to derive the type of the context, which is useful when the type of the directive is generic. For the `SelectDirective` described above, you can implement an `ngTemplateContextGuard` to correctly specify the data type, even if the data source is generic. // Declare an interface for the template context:export interface SelectTemplateContext<T> { $implicit: T;}@Directive(...)export class SelectDirective<T> { // The directive's generic type `T` will be inferred from the `DataSource` type // passed to the input. @Input({required: true}) selectFrom!: 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; }} --- ## Page: https://angular.dev/guide/directives/directive-composition-api Angular directives offer a great way to encapsulate reusable behaviors— directives can apply attributes, CSS classes, and event listeners to an element. The _directive composition API_ lets you apply directives to a component's host element from _within_ the component TypeScript class. ## [Adding directives to a component](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#host-directive-semantics) ### [Directive execution order](https://angular.dev/#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](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/directives/tools/cli/schematics ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/directives/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/directives/api/core/ng-template ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/directives/tools/cli/template-typecheck ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di **TIP:** Check out Angular's [Essentials](https://angular.dev/essentials/dependency-injection) before diving into this comprehensive guide. When you develop a smaller part of your system, like a module or a class, you may need to use features from other classes. For example, you may need an HTTP service to make backend calls. Dependency Injection, or DI, is a design pattern and mechanism for creating and delivering some parts of an application to other parts of an application that require them. Angular supports this design pattern and you can use it in your applications to increase flexibility and modularity. In Angular, dependencies are typically services, but they also can be values, such as strings or functions. An injector for an application (created automatically during bootstrap) instantiates dependencies when needed, using a configured provider of the service or value. ## [Learn about Angular dependency injection](https://angular.dev/#learn-about-angular-dependency-injection) --- ## Page: https://angular.dev/guide/di/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](https://angular.dev/guide/di/dependency-injection-providers). ## [Providing a dependency](https://angular.dev/#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`](https://angular.dev/#preferred-at-the-application-root-level-using-providedin) * [At the Component level](https://angular.dev/#at-the-component-level) * [At the application root level using `ApplicationConfig`](https://angular.dev/#at-the-application-root-level-using-applicationconfig) * [`NgModule` based applications](https://angular.dev/#ngmodule-based-applications) ### [**Preferred**: At the application root level using `providedIn`](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/di/hierarchical-dependency-injection). **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](https://angular.dev/#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](https://angular.dev/guide/di/dependency-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. Injector Service A HeroService Service C Service D Component heroService = inject(HeroService) ## [What's next](https://angular.dev/#whats-next) [Creating an injectable service](https://angular.dev/guide/di/creating-injectable-service) --- ## Page: https://angular.dev/guide/di/creating-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](https://angular.dev/#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](https://angular.dev/#creating-an-injectable-service) 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](https://angular.dev/tools/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](https://angular.dev/#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](https://angular.dev/guide/di/dependency-injection-context), usually in the construction or initialization of a component. ## [Injecting services in other services](https://angular.dev/#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](https://angular.dev/#whats-next) [Configuring dependency providers](https://angular.dev/guide/di/dependency-injection-providers) [`InjectionTokens`](https://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-object) --- ## Page: https://angular.dev/guide/di/dependency-injection-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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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);} --- ## Page: https://angular.dev/guide/di/dependency-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`](https://angular.dev/api/core/inject) function to inject instances. ## [Class constructors](https://angular.dev/#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`](https://angular.dev/api/core/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](https://angular.dev/#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`](https://angular.dev/api/core/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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#using-di-outside-of-a-context) Calling [`inject`](https://angular.dev/api/core/inject) or calling `assertInInjectionContext` outside of an injection context will throw [error NG0203](https://angular.dev/errors/NG0203). --- ## Page: https://angular.dev/guide/di/hierarchical-dependency-injection 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](https://angular.dev/#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()`. | ### NgModule Based Applications For `NgModule` based applications, you can provide dependencies with the `ModuleInjector` hierarchy using an `@NgModule()` or `@Injectable()` annotation. ### [`EnvironmentInjector`](https://angular.dev/#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 ### Tree-shaking and @Injectable() 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#optional) of this guide. The following diagram represents the relationship between the `root` `ModuleInjector` and its parent injectors as the previous paragraphs describe. EnvironmentInjector (configured by Angular) has special things like DomSanitizer => providedIn 'platform' root EnvironmentInjector (configured by AppConfig) has things for your app => bootstrapApplication(..., AppConfig) NullInjector always throws an error unless you use @Optional() 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. ### @Injectable() vs. ApplicationConfig 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](https://angular.dev/guide/routing#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`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#resolution-rules) section. When the component instance is destroyed, so is that service instance. #### [`@Directive()` and `@Component()`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/#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`](https://angular.dev/#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 `🌷`. import {Component, Self} from '@angular/core';import {FlowerService} from '../flower.service';@Component({ selector: 'app-self', templateUrl: './self.component.html', styleUrls: ['./self.component.css'], providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],})export class SelfComponent { constructor(@Self() public flower: FlowerService) {}}// This component provides the FlowerService so the injector// doesn't have to look further up the injector tree ### [`skipSelf`](https://angular.dev/#skipself) `skipSelf` is the opposite of `self`. With `skipSelf`, Angular starts its search for a service in the parent `ElementInjector`, rather than in the current one. So if the parent `ElementInjector` were using the fern `🌿` value for `emoji`, but you had maple leaf `🍁` in the component's `providers` array, Angular would ignore maple leaf `🍁` and use fern `🌿`. To see this in code, assume that the following value for `emoji` is what the parent component were using, as in this service: 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`](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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()`](https://angular.dev/#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](https://angular.dev/#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: 🌺 In the logical tree, this would be represented as follows: <app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child> <#VIEW> </#VIEW> </app-child> </#VIEW></app-root> When `<app-root>` requests the `FlowerService`, it is the injector's job to resolve the `FlowerService` token. The resolution of the token happens in two phases: 1. The injector determines the starting location in the logical tree and an ending location of the search. The injector begins with the starting location and looks for the token at each view level in the logical tree. If the token is found it is returned. 2. If the token is not found, the injector looks for the closest parent `EnvironmentInjector` to delegate the request to. In the example case, the constraints are: 1. Start with `<#VIEW>` belonging to `<app-root>` and end with `<app-root>`. * Normally the starting point for search is at the point of injection. However, in this case `<app-root>` is a component. `@Component`s are special in that they also include their own `viewProviders`, which is why the search starts at `<#VIEW>` belonging to `<app-root>`. This would not be the case for a directive matched at the same location. * The ending location happens to be the same as the component itself, because it is the topmost component in this application. 2. The `EnvironmentInjector` 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](https://angular.dev/#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 ComponentEmoji from FlowerService: 🌻 In the logical tree, this is represented as follows: <app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p> <app-child @Provide(FlowerService="🌻") @Inject(FlowerService)=>"🌻"> <!-- search ends here --> <#VIEW> <!-- search starts here --> <h2>Child Component</h2> <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p> </#VIEW> </app-child> </#VIEW></app-root> When `<app-child>` requests the `FlowerService`, the injector begins its search at the `<#VIEW>` belonging to `<app-child>` (`<#VIEW>` is included because it is injected from `@Component()`) 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](https://angular.dev/#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](https://angular.dev/#visibility-of-provided-tokens). For demonstration, we are building an `AnimalService` to demonstrate `viewProviders`. First, create an `AnimalService` with an `emoji` property of whale `🐳`: import { 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: AppComponentEmoji from AnimalService: 🐳Child ComponentEmoji from AnimalService: 🐶 The logic tree for this example of `viewProviders` is as follows: <app-root @ApplicationConfig @Inject(AnimalService) animal=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^using viewProviders means AnimalService is available in <#VIEW>--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </#VIEW> </app-child> </#VIEW></app-root> Just as with the `FlowerService` example, the `AnimalService` is provided in the `<app-child>` `@Component()` 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`](https://angular.dev/#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 projectionEmoji from FlowerService: 🌻Emoji from AnimalService: 🐳Emoji from FlowerService: 🌻Emoji from AnimalService: 🐶 These four bindings demonstrate the difference between `providers` and `viewProviders`. Remember that the dog emoji `🐶` is declared inside the `<#VIEW>` of `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=>"🐳"> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService=>"🐶")> <!-- ^^using viewProviders means AnimalService is available in <#VIEW>--> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> <div class="container"> <h3>Content projection</h3> <app-inspector @Inject(AnimalService) animal=>"🐳"> <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p> </app-inspector> </div> <app-inspector> <#VIEW @Inject(AnimalService) animal=>"🐶"> <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p> </#VIEW> </app-inspector> </#VIEW> </app-child> </#VIEW></app-root> The projected 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](https://angular.dev/#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: 🌺 In a logical tree, this same idea might look like this: <app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <app-child @Provide(FlowerService="🌻")> <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺"> <!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) --> </#VIEW> </app-child> </#VIEW></app-root> Though `<app-child>` provides the sunflower `🌻`, the application renders the red hibiscus `🌺` because `skipSelf` causes the current injector (`app-child`) to skip itself and look to its parent. If you now add `host` (in addition to the `skipSelf`), the result will be `null`. This is because `host` limits the upper bound of the search to the `app-child` `<#VIEW>`. Here's the idea in the logical tree: <app-root @ApplicationConfig @Inject(FlowerService) flower=>"🌺"> <#VIEW> <!-- end search here with null--> <app-child @Provide(FlowerService="🌻")> <!-- start search here --> <#VIEW inject(FlowerService, {skipSelf: true, host: true, optional:true})=>null> </#VIEW> </app-parent> </#VIEW></app-root> Here, the services and their values are the same, but `host` stops the injector from looking any further than the `<#VIEW>` for `FlowerService`, so it doesn't find it and returns `null`. ### [`skipSelf` and `viewProviders`](https://angular.dev/#skipself-and-viewproviders) Remember, `<app-child>` provides the `AnimalService` in the `viewProviders` array with the value of dog `🐶`. Because the injector has only to look at the `ElementInjector` of the `<app-child>` for the `AnimalService`, it never sees the whale `🐳`. As in the `FlowerService` example, if you add `skipSelf` to the `inject()` of `AnimalService`, the injector won't look in the `ElementInjector` of the current `<app-child>` for the `AnimalService`. Instead, the injector will begin at the `<app-root>` `ElementInjector`. @Component({ selector: 'app-child', … viewProviders: [ { provide: AnimalService, useValue: { emoji: '🐶' } }, ],}) The logical tree looks like this with `skipSelf` in `<app-child>`: <app-root @ApplicationConfig @Inject(AnimalService=>"🐳")> <#VIEW><!-- search begins here --> <app-child> <#VIEW @Provide(AnimalService="🐶") @Inject(AnimalService, SkipSelf=>"🐳")> <!--Add skipSelf --> </#VIEW> </app-child> </#VIEW></app-root> With `skipSelf` in the `<app-child>`, the injector begins its search for the `AnimalService` in the `<app-root>` `ElementInjector` and finds whale `🐳`. ### [`host` and `viewProviders`](https://angular.dev/#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: '🐶' } }, ]})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=>"🐳")> <#VIEW> <app-child> <#VIEW @Provide(AnimalService="🐶") inject(AnimalService, {host: true}=>"🐶")> <!-- host stops search here --> </#VIEW> </app-child> </#VIEW></app-root> Add a `viewProviders` array with a third animal, hedgehog `🦔`, to the `app.component.ts` `@Component()` metadata: @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ], viewProviders: [ { provide: AnimalService, useValue: { emoji: '🦔' } }, ],}) 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=>"🐳")> <#VIEW @Provide(AnimalService="🦔") @Inject(AnimalService, @Optional)=>"🦔"> <!-- ^^skipSelf starts here, host stops here^^ --> <app-child> <#VIEW @Provide(AnimalService="🐶") inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"🦔"> <!-- Add skipSelf ^^--> </#VIEW> </app-child> </#VIEW></app-root> `skipSelf`, causes the injector to start its search for the `AnimalService` at the `<app-root>`, not the `<app-child>`, where the request originates, and `host` stops the search at the `<app-root>` `<#VIEW>`. Since `AnimalService` is provided by way of the `viewProviders` array, the injector finds hedgehog `🦔` in the `<#VIEW>`. ## [Example: `ElementInjector` use cases](https://angular.dev/#example-elementinjector-use-cases) The ability to configure one or more providers at different levels opens up useful possibilities. ### [Scenario: service isolation](https://angular.dev/#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](https://angular.dev/#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 = ''; @Output() close = new EventEmitter<void>(); get taxReturn(): HeroTaxReturn { return this.heroTaxReturnService.taxReturn; } @Input() set taxReturn(htr: HeroTaxReturn) { this.heroTaxReturnService.taxReturn = htr; } 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](https://angular.dev/#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`. Component A Component B Component C 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). (A) RootInjector CarService, EngineService, TiresService (B) ParentInjector CarService2, EngineService2 (C) ChildInjector CarService3 (C) Car (B) Engine (A) Tires ## [More on dependency injection](https://angular.dev/#more-on-dependency-injection) [DI Providers](https://angular.dev/guide/di/dependency-injection-providers) --- ## Page: https://angular.dev/guide/di/lightweight-injection-tokens This page provides a conceptual overview of a dependency injection technique that is recommended for library developers. Designing your library with _lightweight injection tokens_ helps optimize the bundle size of client applications that use your library. You can manage the dependency structure among your components and injectable services to optimize bundle size by using tree-shakable providers. This normally ensures that if a provided component or service is never actually used by the application, the compiler can remove its code from the bundle. Due to the way Angular stores injection tokens, it is possible that such an unused component or service can end up in the bundle anyway. This page describes a dependency injection design pattern that supports proper tree-shaking by using lightweight injection tokens. The lightweight injection token design pattern is especially important for library developers. It ensures that when an application uses only some of your library's capabilities, the unused code can be eliminated from the client's application bundle. When an application uses your library, there might be some services that your library supplies which the client application doesn't use. In this case, the application developer should expect that service to be tree-shaken, and not contribute to the size of the compiled application. Because the application developer cannot know about or remedy a tree-shaking problem in the library, it is the responsibility of the library developer to do so. To prevent the retention of unused components, your library should use the lightweight injection token design pattern. ## [When tokens are retained](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/guide/components/queries#content-queries). * 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](https://angular.dev/guide/di/dependency-injection#providing-dependency) for all services, providing dependencies at the root level rather than in components or modules. ## [Using lightweight injection tokens](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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`." --- ## Page: https://angular.dev/guide/di/di-in-action This guide explores additional features of dependency injection in Angular. ## [Custom providers with `@Inject`](https://angular.dev/#custom-providers-with-inject) Using a custom provider allows you to provide a concrete implementation for implicit dependencies, such as built-in browser APIs. The following example uses an `InjectionToken` to provide the [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) browser API as a dependency in the `BrowserStorageService`: import { Inject, Injectable, InjectionToken } from '@angular/core';export const BROWSER_STORAGE = new InjectionToken<Storage>('Browser Storage', { providedIn: 'root', factory: () => localStorage});@Injectable({ providedIn: 'root'})export class BrowserStorageService { public storage = inject(BROWSER_STORAGE); get(key: string) { return this.storage.getItem(key); } set(key: string, value: string) { this.storage.setItem(key, value); }} The `factory` function returns the `localStorage` property that is attached to the browser's window object. The `inject` function initializes the `storage` property with an instance of the token. This custom provider can now be overridden during testing with a mock API of `localStorage` instead of interacting with real browser APIs. ## [Inject the component's DOM element](https://angular.dev/#inject-the-components-dom-element) Although developers strive to avoid it, some visual effects and third-party tools require direct DOM access. As a result, you might need to access a component's DOM element. Angular exposes the underlying element of a `@Component` or `@Directive` via injection using the `ElementRef` injection token: import { Directive, ElementRef } from '@angular/core';@Directive({ selector: '[appHighlight]'})export class HighlightDirective { private element = inject(ElementRef) update() { this.element.nativeElement.style.color = 'red'; }} ## [Resolve circular dependencies with a forward reference](https://angular.dev/#resolve-circular-dependencies-with-a-forward-reference) The order of class declaration matters in TypeScript. You can't refer directly to a class until it's been defined. This isn't usually a problem, especially if you adhere to the recommended _one class per file_ rule. But sometimes circular references are unavoidable. For example, when class 'A' refers to class 'B' and 'B' refers to 'A', one of them has to be defined first. The Angular `forwardRef()` function creates an _indirect_ reference that Angular can resolve later. You face a similar problem when a class makes _a reference to itself_. For example, in its `providers` array. The `providers` array is a property of the `@Component()` decorator function, which must appear before the class definition. You can break such circular references by using `forwardRef`. providers: [ { provide: PARENT_MENU_ITEM, useExisting: forwardRef(() => MenuItem), },], --- ## Page: https://angular.dev/guide/di/guide/di/dependency-injection-providers ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/guide/di/hierarchical-dependency-injection ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/guide/di/dependency-injection-context ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/api/core/inject ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/guide/routing#location-strategy ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/guide/components/queries#content-queries ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/di/guide/di/dependency-injection#providing-dependency ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing In a single-page app, you change what the user sees by showing or hiding portions of the display that correspond to particular components, rather than going out to the server to get a new page. As users perform application tasks, they need to move between the different views that you have defined. To handle the navigation from one view to the next, you use the Angular **`Router`**. The **`Router`** enables navigation by interpreting a browser URL as an instruction to change the view. ## [Learn about Angular routing](https://angular.dev/#learn-about-angular-routing) --- ## Page: https://angular.dev/guide/routing/common-router-tasks This topic describes how to implement many of the common tasks associated with adding the Angular router to your application. ## [Generate an application with routing enabled](https://angular.dev/#generate-an-application-with-routing-enabled) The following command uses the Angular CLI to generate a basic Angular application with application routes. The application name in the following example is `routing-app`. ng new routing-app ### [Adding components for routing](https://angular.dev/#adding-components-for-routing) To use the Angular router, an application needs to have at least two components so that it can navigate from one to the other. To create a component using the CLI, enter the following at the command line where `first` is the name of your component: ng generate component first Repeat this step for a second component but give it a different name. Here, the new name is `second`. ng generate component second The CLI automatically appends `Component`, so if you were to write `first-component`, your component would be `FirstComponentComponent`. ### `base href` This guide works with a CLI-generated Angular application. If you are working manually, make sure that you have `<base href="/">` in the `<head>` of your index.html file. This assumes that the `app` folder is the application root, and uses `"/"`. ### [Importing your new components](https://angular.dev/#importing-your-new-components) To use your new components, import them into `app.routes.ts` at the top of the file, as follows: import {FirstComponent} from './first/first.component';import {SecondComponent} from './second/second.component'; ## [Defining a basic route](https://angular.dev/#defining-a-basic-route) There are three fundamental building blocks to creating a route. Import the routes into `app.config.ts` and add it to the `provideRouter` function. The following is the default `ApplicationConfig` using the CLI. export const appConfig: ApplicationConfig = { providers: [provideRouter(routes)]}; The Angular CLI performs this step for you. However, if you are creating an application manually or working with an existing, non-CLI application, verify that the imports and configuration are correct. 1. ### [Set up a `Routes` array for your routes](https://angular.dev/#set-up-a-routes-array-for-your-routes) The Angular CLI performs this step automatically. import { Routes } from '@angular/router';export const routes: Routes = []; 2. ### [Define your routes in your `Routes` array](https://angular.dev/#define-your-routes-in-your-routes-array) Each route in this array is a JavaScript object that contains two properties. The first property, `path`, defines the URL path for the route. The second property, `component`, defines the component Angular should use for the corresponding path. const routes: Routes = [ { path: 'first-component', component: FirstComponent }, { path: 'second-component', component: SecondComponent },]; 3. ### [Add your routes to your application](https://angular.dev/#add-your-routes-to-your-application) Now that you have defined your routes, add them to your application. First, add links to the two components. Assign the anchor tag that you want to add the route to the `routerLink` attribute. Set the value of the attribute to the component to show when a user clicks on each link. Next, update your component template to include `<router-outlet>`. This element informs Angular to update the application view with the component for the selected route. <h1>Angular Router App</h1><nav> <ul> <li><a routerLink="/first-component" routerLinkActive="active" ariaCurrentWhenActive="page">First Component</a></li> <li><a routerLink="/second-component" routerLinkActive="active" ariaCurrentWhenActive="page">Second Component</a></li> </ul></nav><!-- The routed views render in the <router-outlet>--><router-outlet /> You also need to add the `RouterLink`, `RouterLinkActive`, and `RouterOutlet` to the `imports` array of `AppComponent`. @Component({ selector: 'app-root', imports: [RouterOutlet, RouterLink, RouterLinkActive], templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { title = 'routing-app';} ### [Route order](https://angular.dev/#route-order) The order of routes is important because the `Router` uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. List routes with a static path first, followed by an empty path route, which matches the default route. The [wildcard route](https://angular.dev/guide/routing/common-router-tasks#setting-up-wildcard-routes) comes last because it matches every URL and the `Router` selects it only if no other routes match first. ## [Getting route information](https://angular.dev/#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](https://angular.dev/api/router/withComponentInputBinding) feature with `provideRouter` or the `bindToComponentInputs` option of `RouterModule.forRoot`. To get information from a route: 1. ### [Add `withComponentInputBinding`](https://angular.dev/#add-withcomponentinputbinding) Add the `withComponentInputBinding` feature to the `provideRouter` method. providers: [ provideRouter(appRoutes, withComponentInputBinding()),] 2. ### [Add an `Input` to the component](https://angular.dev/#add-an-input-to-the-component) Update the component to have an `Input` matching the name of the parameter. @Input()set id(heroId: string) { this.hero$ = this.service.getHero(heroId);} 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'})` ## [Setting up wildcard routes](https://angular.dev/#setting-up-wildcard-routes) A well-functioning application should gracefully handle when users attempt to navigate to a part of your application that does not exist. To add this functionality to your application, you set up a wildcard route. The Angular router selects this route any time the requested URL doesn't match any router paths. To set up a wildcard route, add the following code to your `routes` definition. { path: '**', component: <component-name> } The two asterisks, `**`, indicate to Angular that this `routes` definition is a wildcard route. For the component property, you can define any component in your application. Common choices include an application-specific `PageNotFoundComponent`, which you can define to [display a 404 page](https://angular.dev/guide/routing/common-router-tasks#displaying-a-404-page) to your users; or a redirect to your application's main component. A wildcard route is the last route because it matches any URL. For more detail on why order matters for routes, see [Route order](https://angular.dev/guide/routing/common-router-tasks#route-order). ## [Displaying a 404 page](https://angular.dev/#displaying-a-404-page) To display a 404 page, set up a [wildcard route](https://angular.dev/guide/routing/common-router-tasks#setting-up-wildcard-routes) 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`. ## [Setting up redirects](https://angular.dev/#setting-up-redirects) To set up a redirect, configure a route with the `path` you want to redirect from, the `component` you want to redirect to, and a `pathMatch` value that tells the router how to match the URL. const routes: Routes = [ { path: 'first-component', component: FirstComponent }, { path: 'second-component', component: SecondComponent }, { path: '', redirectTo: '/first-component', pathMatch: 'full' }, // redirect to `first-component` { path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page]; In this example, the third route is a redirect so that the router defaults to the `first-component` route. Notice that this redirect precedes the wildcard route. Here, `path: ''` means to use the initial relative URL (`''`). Sometimes a redirect is not a simple, static redirect. The `redirectTo` property can also be a function with more complex logic that returns a string or `UrlTree`. const routes: Routes = [ { path: "first-component", component: FirstComponent }, { path: "old-user-page", redirectTo: ({ queryParams }) => { const errorHandler = inject(ErrorHandler); const userIdParam = queryParams['userId']; if (userIdParam !== undefined) { return `/user/${userIdParam}`; } else { errorHandler.handleError(new Error('Attempted navigation to user page without user ID.')); return `/not-found`; } }, }, { path: "user/:userId", component: OtherComponent },]; ## [Nesting routes](https://angular.dev/#nesting-routes) As your application grows more complex, you might want to create routes that are relative to a component other than your root component. 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, there are two additional child components, `child-a`, and `child-b`. Here, `FirstComponent` has its own `<nav>` and a second `<router-outlet>` in addition to the one in `AppComponent`. <h2>First Component</h2><nav> <ul> <li><a routerLink="child-a">Child A</a></li> <li><a routerLink="child-b">Child B</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: 'first-component', component: FirstComponent, // this is the component with the <router-outlet> in the template children: [ { path: 'child-a', // child route path component: ChildAComponent, // child route component that the router renders }, { path: 'child-b', component: ChildBComponent, // another child route component that the router renders }, ], },]; ## [Setting the page title](https://angular.dev/#setting-the-page-title) Each page in your application should have a unique title so that they can be identified in the browser history. The `Router` sets the document's title using the `title` property from the `Route` config. const routes: Routes = [ { path: 'first-component', title: 'First component', component: FirstComponent, // this is the component with the <router-outlet> in the template children: [ { path: 'child-a', // child route path title: resolvedChildATitle, component: ChildAComponent, // child route component that the router renders }, { path: 'child-b', title: 'child b', component: ChildBComponent, // another child route component that the router renders }, ], },];const resolvedChildATitle: ResolveFn<string> = () => Promise.resolve('child a'); **HELPFUL:** The `title` property follows the same rules as static route `data` and dynamic values that implement `ResolveFn`. You can also provide a custom title strategy by extending the `TitleStrategy`. @Injectable({ providedIn: 'root' })export class TemplatePageTitleStrategy extends TitleStrategy { private readonly title = inject(Title); override updateTitle(routerState: RouterStateSnapshot) { const title = this.buildTitle(routerState); if (title !== undefined) { this.title.setTitle(`My Application | ${title}`); } }}export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), { provide: TitleStrategy, useClass: TemplatePageTitleStrategy }, ]}; ## [Using relative paths](https://angular.dev/#using-relative-paths) Relative paths let you define paths that are relative to the current URL segment. The following example shows a relative route to another component, `second-component`. `FirstComponent` and `SecondComponent` are at the same level in the tree, however, the link to `SecondComponent` is situated within the `FirstComponent`, meaning that the router has to go up a level and then into the second directory to find the `SecondComponent`. Rather than writing out the whole path to get to `SecondComponent`, use the `../` notation to go up a level. <h2>First Component</h2><nav> <ul> <li><a routerLink="../second-component">Relative Route to second component</a></li> </ul></nav><router-outlet /> In addition to `../`, use `./` or no leading slash to specify the current level. ### [Specifying a relative route](https://angular.dev/#specifying-a-relative-route) To specify a relative route, use the `NavigationExtras` `relativeTo` property. In the component class, import `NavigationExtras` from the `@angular/router`. Then use `relativeTo` in your navigation method. After the link parameters array, which here contains `items`, add an object with the `relativeTo` property set to the `ActivatedRoute`, which is `this.route`. goToItems() { this.router.navigate(['items'], { relativeTo: this.route });} The `navigate()` arguments configure the router to use the current route as a basis upon which to append `items`. The `goToItems()` method interprets the destination URI as relative to the activated route and navigates to the `items` route. ## [Accessing query parameters and fragments](https://angular.dev/#accessing-query-parameters-and-fragments) Sometimes, a feature of your application requires accessing a part of a route, such as a query parameter or a fragment. In this example, the route contains an `id` parameter we can use to target a specific hero page. import { ApplicationConfig } from "@angular/core";import { Routes } from '@angular/router';import { HeroListComponent } from './hero-list.component';export const routes: Routes = [ { path: 'hero/:id', component: HeroDetailComponent }];export const appConfig: ApplicationConfig = { providers: [provideRouter(routes)],}; First, import the following members in the component you want to navigate from. import { inject } from '@angular/core';import { ActivatedRoute } from '@angular/router';import { Observable, switchMap } from 'rxjs'; Next inject the activated route service: private readonly route = inject(ActivatedRoute); Configure the class so that you have an observable, `heroes$`, a `selectedId` to hold the `id` number of the hero, and the heroes in the `ngOnInit()`, add the following code to get the `id` of the selected hero. This code snippet assumes that you have a heroes list, a hero service, a function to get your heroes, and the HTML to render your list and details, just as in the Tour of Heroes example. heroes$: Observable<Hero[]>;selectedId: number;heroes = HEROES;ngOnInit() { this.heroes$ = this.route.paramMap.pipe( switchMap(params => { this.selectedId = Number(params.get('id')); return this.service.getHeroes(); }) );} Next, in the component that you want to navigate to, import the following members. import { Router, ActivatedRoute, ParamMap } from '@angular/router';import { Observable } from 'rxjs'; Inject `ActivatedRoute` and `Router` in the constructor of the component class so they are available to this component: private readonly route = inject(ActivatedRoute);private readonly router = inject(Router);hero$: Observable<Hero>;ngOnInit() { const heroId = this.route.snapshot.paramMap.get('id'); this.hero$ = this.service.getHero(heroId);}gotoItems(hero: Hero) { const heroId = hero ? hero.id : null; // Pass along the hero id if available // so that the HeroList component can select that item. this.router.navigate(['/heroes', { id: heroId }]);} ## [Lazy loading](https://angular.dev/#lazy-loading) You can configure your routes to lazy load modules, which means that Angular only loads modules as needed, rather than loading all modules when the application launches. Additionally, preload parts of your application in the background to improve the user experience. Any route can lazily load its routed, standalone component by using `loadComponent:` const routes: Routes = [ { path: 'lazy', loadComponent: () => import('./lazy.component').then(c => c.LazyComponent) }]; This works as long as the loaded component is standalone. Use route guards to prevent users from navigating to parts of an application without authorization. The following route guards are available in Angular: [`canActivate`](https://angular.dev/api/router/CanActivateFn) [`canActivateChild`](https://angular.dev/api/router/CanActivateChildFn) [`canDeactivate`](https://angular.dev/api/router/CanDeactivateFn) [`canMatch`](https://angular.dev/api/router/CanMatchFn) [`resolve`](https://angular.dev/api/router/ResolveFn) [`canLoad`](https://angular.dev/api/router/CanLoadFn) To use route guards, consider using [component-less routes](https://angular.dev/api/router/Route#componentless-routes) as this facilitates guarding child routes. Create a file for your guard: ng generate guard your-guard In your guard file, add the guard functions you want to use. The following example uses `canActivateFn` to guard the route. export const yourGuardFunction: CanActivateFn = ( next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { // your logic goes here} In your routing module, use the appropriate property in your `routes` configuration. Here, `canActivate` tells the router to mediate navigation to this particular route. { path: '/your-path', component: YourComponent, canActivate: [yourGuardFunction],} ## [Link parameters array](https://angular.dev/#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](https://angular.dev/#locationstrategy-and-browser-url-styles) When the router navigates to a new component view, it updates the browser's location and history with a URL for that view. Modern HTML5 browsers support [history.pushState](https://developer.mozilla.org/docs/Web/API/History_API/Working_with_the_History_API#adding_and_modifying_history_entries "HTML5"), 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](https://angular.dev/guide/di/dependency-injection-providers). ## [Choosing a routing strategy](https://angular.dev/#choosing-a-routing-strategy) You must choose a routing strategy early in the development of your project because once the application is in production, visitors to your site use and depend on application URL references. Almost all Angular projects should use the default HTML5 style. It produces URLs that are easier for users to understand and it preserves the option to do server-side rendering. Rendering critical pages on the server is a technique that can greatly improve perceived responsiveness when the application first loads. An application that would otherwise take ten or more seconds to start could be rendered on the server and delivered to the user's device in less than a second. This option is only available if application URLs look like normal web URLs without hash (`#`) characters in the middle. ## [`<base href>`](https://angular.dev/#base-href) The router uses the browser's [history.pushState](https://developer.mozilla.org/docs/Web/API/History_API/Working_with_the_History_API#adding_and_modifying_history_entries "HTML5") for navigation. `pushState` lets you customize in-application URL paths; for example, `localhost:4200/crisis-center`. The in-application URLs can be indistinguishable from server URLs. Modern HTML5 browsers were the first to support `pushState` which is why many people refer to these URLs as "HTML5 style" URLs. **HELPFUL:** HTML5 style navigation is the router default. In the [LocationStrategy and browser URL styles](https://angular.dev/#locationstrategy-and-browser-url-styles) section, learn why HTML5 style is preferable, how to adjust its behavior, and how to switch to the older hash (`#`) style, if necessary. You must add a [`<base href>` element](https://developer.mozilla.org/docs/Web/HTML/Element/base "base") to the application's `index.html` for `pushState` routing to work. The browser uses the `<base href>` value to prefix relative URLs when referencing CSS files, scripts, and images. Add the `<base>` element just after the `<head>` tag. If the `app` folder is the application root, as it is for this application, set the `href` value in `index.html` as shown here. <!DOCTYPE html><html lang="en"> <head> <!-- Set the base href --> <base href="/"> <title>Angular Router</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <app-root></app-root> </body></html> ### [HTML5 URLs and the `<base href>`](https://angular.dev/#html5-urls-and-the-base-href) The guidelines that follow will refer to different parts of a URL. This diagram outlines what those parts refer to: foo://example.com:8042/over/there?name=ferret#nose\_/ \______________/\_________/ \_________/ \__/ | | | | |scheme authority path query fragment While the router uses the [HTML5 pushState](https://developer.mozilla.org/docs/Web/API/History_API#Adding_and_modifying_history_entries "Browser") style by default, you must configure that strategy with a `<base href>`. The preferred way to configure the strategy is to add a [`<base href>` element](https://developer.mozilla.org/docs/Web/HTML/Element/base "base") tag in the `<head>` of the `index.html`. <base href="/"> Without that tag, the browser might not be able to load resources (images, CSS, scripts) when "deep linking" into the application. Some developers might not be able to add the `<base>` element, perhaps because they don't have access to `<head>` or the `index.html`. Those developers can still use HTML5 URLs by taking the following two steps: 1. Provide the router with an appropriate `APP_BASE_HREF` value. 2. Use root URLs (URLs with an `authority`) for all web resources: CSS, images, scripts, and template HTML files. * The `<base href>` `path` should end with a "/", as browsers ignore characters in the `path` that follow the right-most "`/`" * If the `<base href>` includes a `query` part, the `query` is only used if the `path` of a link in the page is empty and has no `query`. This means that a `query` in the `<base href>` is only included when using `HashLocationStrategy`. * If a link in the page is a root URL (has an `authority`), the `<base href>` is not used. In this way, an `APP_BASE_HREF` with an authority will cause all links created by Angular to ignore the `<base href>` value. * A fragment in the `<base href>` is _never_ persisted For more complete information on how `<base href>` is used to construct target URIs, see the [RFC](https://tools.ietf.org/html/rfc3986#section-5.2.2) section on transforming references. ### [`HashLocationStrategy`](https://angular.dev/#hashlocationstrategy) Use `HashLocationStrategy` by providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot()` in the `AppModule`. providers: [ provideRouter(appRoutes, withHashLocation())] When using `RouterModule.forRoot`, this is configured with the `useHash: true` in the second argument: `RouterModule.forRoot(routes, {useHash: true})`. --- ## Page: https://angular.dev/guide/routing/router-tutorial 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](https://angular.dev/#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](https://angular.dev/#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. 2. From your terminal, navigate to the `angular-router-sample` directory. 3. Create a component, _crisis-list_. ng generate component crisis-list 4. In your code editor, locate the file, `crisis-list.component.html` and replace the placeholder content with the following HTML. <h3>CRISIS CENTER</h3><p>Get your crisis here</p> 5. Create a second component, _heroes-list_. ng generate component heroes-list 6. In your code editor, locate the file, `heroes-list.component.html` and replace the placeholder content with the following HTML. <h3>HEROES</h3><p>Get your heroes here</p> 7. In your code editor, open the file, `app.component.html` and replace its contents with the following HTML. <h1>Angular Router Sample</h1><nav> <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Crisis Center </a> | <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Heroes </a></nav><router-outlet></router-outlet><div style="display: none;"><!-- This HTML represents the initial state for the tutorial. It is not intended to appear in the app. --><app-crisis-list></app-crisis-list><app-heroes-list></app-heroes-list><!-- This HTML snippet is for when the user first adds the routerlink navigation. --><nav> <a class="button" routerLink="/crisis-list">Crisis Center</a> | <a class="button" routerLink="/heroes-list">Heroes</a></nav></div> 8. Verify that your new application runs as expected by running the `ng serve` command. ng serve 9. 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](https://angular.dev/#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 = []; 3. 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`](https://angular.dev/#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'; 3. 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`](https://angular.dev/#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. <h1>Angular Router Sample</h1><nav> <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Crisis Center </a> | <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Heroes </a></nav><router-outlet></router-outlet><div style="display: none;"><!-- This HTML represents the initial state for the tutorial. It is not intended to appear in the app. --><app-crisis-list></app-crisis-list><app-heroes-list></app-heroes-list><!-- This HTML snippet is for when the user first adds the routerlink navigation. --><nav> <a class="button" routerLink="/crisis-list">Crisis Center</a> | <a class="button" routerLink="/heroes-list">Heroes</a></nav></div> 3. Add the `router-outlet` directive. <h1>Angular Router Sample</h1><nav> <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Crisis Center </a> | <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Heroes </a></nav><router-outlet></router-outlet><div style="display: none;"><!-- This HTML represents the initial state for the tutorial. It is not intended to appear in the app. --><app-crisis-list></app-crisis-list><app-heroes-list></app-heroes-list><!-- This HTML snippet is for when the user first adds the routerlink navigation. --><nav> <a class="button" routerLink="/crisis-list">Crisis Center</a> | <a class="button" routerLink="/heroes-list">Heroes</a></nav></div> 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](https://angular.dev/#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. <h1>Angular Router Sample</h1><nav> <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Crisis Center </a> | <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Heroes </a></nav><router-outlet></router-outlet><div style="display: none;"><!-- This HTML represents the initial state for the tutorial. It is not intended to appear in the app. --><app-crisis-list></app-crisis-list><app-heroes-list></app-heroes-list><!-- This HTML snippet is for when the user first adds the routerlink navigation. --><nav> <a class="button" routerLink="/crisis-list">Crisis Center</a> | <a class="button" routerLink="/heroes-list">Heroes</a></nav></div> This HTML uses an Angular directive, `routerLink`. This directive connects the routes you defined to your template files. 2. Add the `RouterLink` directive to the imports list of `AppComponent` in `app.component.ts`. 3. Open the `app.component.css` file and add the following styles. .button { box-shadow: inset 0 1px 0 0 #ffffff; background: #ffffff linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%); border-radius: 6px; border: 1px solid #dcdcdc; display: inline-block; cursor: pointer; color: #666666; font-family: Arial, sans-serif; font-size: 15px; font-weight: bold; padding: 6px 24px; text-decoration: none; text-shadow: 0 1px 0 #ffffff; outline: 0;}.activebutton { box-shadow: inset 0 1px 0 0 #dcecfb; background: #bddbfa linear-gradient(to bottom, #bddbfa 5%, #80b5ea 100%); border: 1px solid #84bbf3; color: #ffffff; text-shadow: 0 1px 0 #528ecc;} 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](https://angular.dev/#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. <h1>Angular Router Sample</h1><nav> <a class="button" routerLink="/crisis-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Crisis Center </a> | <a class="button" routerLink="/heroes-list" routerLinkActive="activebutton" ariaCurrentWhenActive="page"> Heroes </a></nav><router-outlet></router-outlet><div style="display: none;"><!-- This HTML represents the initial state for the tutorial. It is not intended to appear in the app. --><app-crisis-list></app-crisis-list><app-heroes-list></app-heroes-list><!-- This HTML snippet is for when the user first adds the routerlink navigation. --><nav> <a class="button" routerLink="/crisis-list">Crisis Center</a> | <a class="button" routerLink="/heroes-list">Heroes</a></nav></div> 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](https://angular.dev/best-practices/a11y#active-links-identification). ## [Adding a redirect](https://angular.dev/#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](https://angular.dev/api/router/Route). | Now when you open your application, it displays the `heroes-list` component by default. ## [Adding a 404 page](https://angular.dev/#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 2. From your code editor, open the `page-not-found.component.html` file and replace its contents with the following HTML. <h2>Page Not Found</h2><p>We couldn't find that page! Not even with x-ray vision.</p> 3. Open the `app.routes.ts` file and add the following route to the routes list: {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](https://angular.dev/#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: [In-app Routing and Navigation](https://angular.dev/guide/routing/common-router-tasks) [Router API](https://angular.dev/api/router/Router) --- ## Page: https://angular.dev/guide/routing/routing-with-urlmatcher The Angular Router supports a powerful matching strategy that you can use to help users navigate your application. This matching strategy supports static routes, variable routes with parameters, wildcard routes, and so on. Also, build your own custom pattern matching for situations in which the URLs are more complicated. In this tutorial, you'll build a custom route matcher using Angular's `UrlMatcher`. This matcher looks for a Twitter handle in the URL. ## [Objectives](https://angular.dev/#objectives) Implement Angular's `UrlMatcher` to create a custom route matcher. ## [Create a sample application](https://angular.dev/#create-a-sample-application) Using the Angular CLI, create a new application, _angular-custom-route-match_. In addition to the default Angular application framework, you will also create a _profile_ component. 1. Create a new Angular project, _angular-custom-route-match_. ng new angular-custom-route-match When prompted with `Would you like to add Angular routing?`, select `Y`. When prompted with `Which stylesheet format would you like to use?`, select `CSS`. After a few moments, a new project, `angular-custom-route-match`, is ready. 2. From your terminal, navigate to the `angular-custom-route-match` directory. 3. Create a component, _profile_. ng generate component profile 4. In your code editor, locate the file, `profile.component.html` and replace the placeholder content with the following HTML. <p> Hello {{ username }}!</p> 5. In your code editor, locate the file, `app.component.html` and replace the placeholder content with the following HTML. <h2>Routing with Custom Matching</h2>Navigate to <a routerLink="/@Angular">my profile</a><router-outlet></router-outlet> ## [Configure your routes for your application](https://angular.dev/#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. 2. Add an `import` statement for Angular's `provideRouter` and `withComponentInputBinding` as well as the application routes. import {provideRouter, withComponentInputBinding} from '@angular/router'; import {routes} from './app.routes'; 3. In the providers array, add a `provideRouter(routes, withComponentInputBinding())` statement. 4. Define the custom route matcher by adding the following code to the application routes. import {Routes, UrlSegment} from '@angular/router';import {ProfileComponent} from './profile/profile.component';export const routes: Routes = [ { matcher: (url) => { if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) { return {consumed: url, posParams: {username: new UrlSegment(url[0].path.slice(1), {})}}; } return null; }, component: ProfileComponent, },]; 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](https://angular.dev/#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. @Input() username!: string; ## [Test your custom URL matcher](https://angular.dev/#test-your-custom-url-matcher) With your code in place, you can now test your custom URL matcher. 1. From a terminal window, run the `ng serve` command. ng serve 2. Open a browser to `http://localhost:4200`. You should see a single web page, consisting of a sentence that reads `Navigate to my profile`. 3. Click the **my profile** hyperlink. A new sentence, reading `Hello, Angular!` appears on the page. ## [Next steps](https://angular.dev/#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: [In-app Routing and Navigation](https://angular.dev/guide/routing/common-router-tasks) [Router API](https://angular.dev/api/router/Router) --- ## Page: https://angular.dev/guide/routing/router-reference The following sections highlight some core router concepts. ## [Router imports](https://angular.dev/#router-imports) The Angular Router is an optional service that presents a particular component view for a given URL. It isn't part of the Angular core and thus is in its own library package, `@angular/router`. Import what you need from it as you would from any other Angular package. import { provideRouter } from '@angular/router'; ## [Configuration](https://angular.dev/#configuration) A routed Angular application has one singleton instance of the `Router` service. When the browser's URL changes, that router looks for a corresponding `Route` from which it can determine the component to display. A router has no routes until you configure it. The following example creates five route definitions, configures the router via the `provideRouter` method, and adds the result to the `providers` array of the `ApplicationConfig`'. const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, { path: 'hero/:id', component: HeroDetailComponent }, { path: 'heroes', component: HeroListComponent, data: { title: 'Heroes List' } }, { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent }];export const appConfig: ApplicationConfig = { providers: [provideRouter(appRoutes, withDebugTracing())]} The `routes` array of routes describes how to navigate. Pass it to the `provideRouter` method in the `ApplicationConfig` `providers` to configure the router. Each `Route` maps a URL `path` to a component. There are no leading slashes in the path. The router parses and builds the final URL for you, which lets you use both relative and absolute paths when navigating between application views. The `:id` in the second route is a token for a route parameter. In a URL such as `/hero/42`, "42" is the value of the `id` parameter. The corresponding `HeroDetailComponent` uses that value to find and present the hero whose `id` is 42. The `data` property in the third route is a place to store arbitrary data associated with this specific route. The data property is accessible within each activated route. Use it to store items such as page titles, breadcrumb text, and other read-only, static data. Use the resolve guard to retrieve dynamic data. The empty path in the fourth route represents the default path for the application —the place to go when the path in the URL is empty, as it typically is at the start. This default route redirects to the route for the `/heroes` URL and, therefore, displays the `HeroesListComponent`. If you need to see what events are happening during the navigation lifecycle, there is the `withDebugTracing` feature. This outputs each router event that took place during each navigation lifecycle to the browser console. Use `withDebugTracing` only for debugging purposes. You set the `withDebugTracing` option in the object passed as the second argument to the `provideRouter` method. ## [Router outlet](https://angular.dev/#router-outlet) The `RouterOutlet` is a directive from the router library that is used like a component. It acts as a placeholder that marks the spot in the template where the router should display the components for that outlet. <router-outlet></router-outlet><!-- Routed components go here --> Given the preceding configuration, when the browser URL for this application becomes `/heroes`, the router matches that URL to the route path `/heroes` and displays the `HeroListComponent` as a sibling element to the `RouterOutlet` that you've placed in the host component's template. ## [Router links](https://angular.dev/#router-links) To navigate as a result of some user action such as the click of an anchor tag, use `RouterLink`. Consider the following template: <h1>Angular Router</h1><nav> <a routerLink="/crisis-center" routerLinkActive="active" ariaCurrentWhenActive="page">Crisis Center</a> <a routerLink="/heroes" routerLinkActive="active" ariaCurrentWhenActive="page">Heroes</a></nav><router-outlet></router-outlet> The `RouterLink` directives on the anchor tags give the router control over those elements. The navigation paths are fixed, so you can assign a string as a one-time binding to the `routerLink`. Had the navigation path been more dynamic, you could have bound to a template expression that returned an array of route link parameters; that is, the [link parameters array](https://angular.dev/guide/routing/common-router-tasks#link-parameters-array). The router resolves that array into a complete URL. ## [Active router links](https://angular.dev/#active-router-links) The `RouterLinkActive` directive toggles CSS classes for active `RouterLink` bindings based on the current `RouterState`. On each anchor tag, you see a [property binding](https://angular.dev/guide/templates/property-binding) to the `RouterLinkActive` directive that looks like routerLinkActive="..." The template expression to the right of the equal sign, `=`, contains a space-delimited string of CSS classes that the Router adds when this link is active and removes when the link is inactive. You set the `RouterLinkActive` directive to a string of classes such as `routerLinkActive="active fluffy"` or bind it to a component property that returns such a string. For example, [routerLinkActive]="someStringProperty" Active route links cascade down through each level of the route tree, so parent and child router links can be active at the same time. To override this behavior, bind to the `[routerLinkActiveOptions]` input binding with the `{ exact: true }` expression. By using `{ exact: true }`, a given `RouterLink` is only active if its URL is an exact match to the current URL. `RouterLinkActive` also allows you to easily apply the `aria-current` attribute to the active element, thus providing a more accessible experience for all users. For more information see the Accessibility Best Practices [Active links identification section](https://angular.dev/best-practices/a11y#active-links-identification). ## [Router state](https://angular.dev/#router-state) After the end of each successful navigation lifecycle, the router builds a tree of `ActivatedRoute` objects that make up the current state of the router. You can access the current `RouterState` from anywhere in the application using the `Router` service and the `routerState` property. Each `ActivatedRoute` in the `RouterState` provides methods to traverse up and down the route tree to get information from parent, child, and sibling routes. ## [Activated route](https://angular.dev/#activated-route) The route path and parameters are available through an injected router service called the [ActivatedRoute](https://angular.dev/api/router/ActivatedRoute). It has a great deal of useful information including: | 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. | | `paramMap` | An `Observable` that contains a [map](https://angular.dev/api/router/ParamMap) of the required and optional parameters specific to the route. The map supports retrieving single and multiple values from the same parameter. | | `queryParamMap` | An `Observable` that contains a [map](https://angular.dev/api/router/ParamMap) of the query parameters available to all routes. The map supports retrieving single and multiple values from the query parameter. | | `queryParams` | An `Observable` that contains the query parameters available to all routes. | | `fragment` | An `Observable` of the URL fragment available to all routes. | | `outlet` | The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is primary. | | `routeConfig` | The route configuration used for the route that contains the origin path. | | `parent` | The route's parent `ActivatedRoute` when this route is a child route. | | `firstChild` | Contains the first `ActivatedRoute` in the list of this route's child routes. | | `children` | Contains all the child routes activated under the current route. | ## [Router events](https://angular.dev/#router-events) During each navigation, the `Router` emits navigation events through the `Router.events` property. These events are shown in the following table. | Router event | Details | | --- | --- | | [`NavigationStart`](https://angular.dev/api/router/NavigationStart) | Triggered when navigation starts. | | [`RouteConfigLoadStart`](https://angular.dev/api/router/RouteConfigLoadStart) | Triggered before the `Router` lazy loads a route configuration. | | [`RouteConfigLoadEnd`](https://angular.dev/api/router/RouteConfigLoadEnd) | Triggered after a route has been lazy loaded. | | [`RoutesRecognized`](https://angular.dev/api/router/RoutesRecognized) | Triggered when the Router parses the URL and the routes are recognized. | | [`GuardsCheckStart`](https://angular.dev/api/router/GuardsCheckStart) | Triggered when the Router begins the Guards phase of routing. | | [`ChildActivationStart`](https://angular.dev/api/router/ChildActivationStart) | Triggered when the Router begins activating a route's children. | | [`ActivationStart`](https://angular.dev/api/router/ActivationStart) | Triggered when the Router begins activating a route. | | [`GuardsCheckEnd`](https://angular.dev/api/router/GuardsCheckEnd) | Triggered when the Router finishes the Guards phase of routing successfully. | | [`ResolveStart`](https://angular.dev/api/router/ResolveStart) | Triggered when the Router begins the Resolve phase of routing. | | [`ResolveEnd`](https://angular.dev/api/router/ResolveEnd) | Triggered when the Router finishes the Resolve phase of routing successfully. | | [`ChildActivationEnd`](https://angular.dev/api/router/ChildActivationEnd) | Triggered when the Router finishes activating a route's children. | | [`ActivationEnd`](https://angular.dev/api/router/ActivationEnd) | Triggered when the Router finishes activating a route. | | [`NavigationEnd`](https://angular.dev/api/router/NavigationEnd) | Triggered when navigation ends successfully. | | [`NavigationCancel`](https://angular.dev/api/router/NavigationCancel) | Triggered when navigation is canceled. This can happen when a Route Guard returns false during navigation, or redirects by returning a `UrlTree` or `RedirectCommand`. | | [`NavigationError`](https://angular.dev/api/router/NavigationError) | Triggered when navigation fails due to an unexpected error. | | [`Scroll`](https://angular.dev/api/router/Scroll) | Represents a scrolling event. | When you enable the `withDebugTracing` feature, Angular logs these events to the console. ## [Router terminology](https://angular.dev/#router-terminology) Here are the key `Router` terms and their meanings: | Router part | Details | | --- | --- | | `Router` | Displays the application component for the active URL. Manages navigation from one component to the next. | | `provideRouter` | provides the necessary service providers for navigating through application views. | | `RouterModule` | A separate NgModule that provides the necessary service providers and directives for navigating through application views. | | `Routes` | Defines an array of Routes, each mapping a URL path to a component. | | `Route` | Defines how the router should navigate to a component based on a URL pattern. Most routes consist of a path and a component type. | | `RouterOutlet` | The directive (`<router-outlet>`) that marks where the router displays a view. | | `RouterLink` | The directive for binding a clickable HTML element to a route. Clicking an element with a `routerLink` directive that's bound to a _string_ or a _link parameters array_ triggers a navigation. | | `RouterLinkActive` | The directive for adding/removing classes from an HTML element when an associated `routerLink` contained on or inside the element becomes active/inactive. It can also set the `aria-current` of an active link for better accessibility. | | `ActivatedRoute` | A service that's provided to each route component that contains route specific information such as route parameters, static data, resolve data, global query parameters, and the global fragment. | | `RouterState` | The current state of the router including a tree of the currently activated routes together with convenience methods for traversing the route tree. | | Link parameters array | An array that the router interprets as a routing instruction. You can bind that array to a `RouterLink` or pass the array as an argument to the `Router.navigate` method. | | Routing component | An Angular component with a `RouterOutlet` that displays views based on router navigations. | --- ## Page: https://angular.dev/guide/routing/guide/routing/common-router-tasks#setting-up-wildcard-routes ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/withComponentInputBinding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/CanActivateFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/CanActivateChildFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/CanDeactivateFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/CanMatchFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ResolveFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/CanLoadFn ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/Route#componentless-routes ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/guide/di/dependency-injection-providers ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/Router ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/guide/templates/property-binding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ActivatedRoute ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ParamMap ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/NavigationStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/RouteConfigLoadStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/RouteConfigLoadEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/RoutesRecognized ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/GuardsCheckStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ChildActivationStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ActivationStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/GuardsCheckEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ResolveStart ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ResolveEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ChildActivationEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/ActivationEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/NavigationEnd ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/NavigationCancel ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/NavigationError ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/routing/api/router/Scroll ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms Applications use forms to enable users to log in, to update a profile, to enter sensitive information, and to perform many other data-entry tasks. Angular provides two different approaches to handling user input through forms: reactive and template-driven. Both capture user input events from the view, validate the user input, create a form model and data model to update, and provide a way to track changes. This guide provides information to help you decide which type of form works best for your situation. It introduces the common building blocks used by both approaches. It also summarizes the key differences between the two approaches, and demonstrates those differences in the context of setup, data flow, and testing. ## [Choosing an approach](https://angular.dev/#choosing-an-approach) Reactive forms and template-driven forms process and manage form data differently. Each approach offers different advantages. | Forms | Details | | --- | --- | | Reactive forms | Provide direct, explicit access to the underlying form's object model. Compared to template-driven forms, they are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms. | | Template-driven forms | Rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They're straightforward to add to an app, but they don't scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit. | ### [Key differences](https://angular.dev/#key-differences) The following table summarizes the key differences between reactive and template-driven forms. | | Reactive | Template-driven | | --- | --- | --- | | [Setup of form model](https://angular.dev/#setting-up-the-form-model) | Explicit, created in component class | Implicit, created by directives | | [Data model](https://angular.dev/#mutability-of-the-data-model) | Structured and immutable | Unstructured and mutable | | [Data flow](https://angular.dev/#data-flow-in-forms) | Synchronous | Asynchronous | | [Form validation](https://angular.dev/#form-validation) | Functions | Directives | ### [Scalability](https://angular.dev/#scalability) If forms are a central part of your application, scalability is very important. Being able to reuse form models across components is critical. Reactive forms are more scalable than template-driven forms. They provide direct access to the underlying form API, and use [synchronous data flow](https://angular.dev/#data-flow-in-reactive-forms) between the view and the data model, which makes creating large-scale forms easier. Reactive forms require less setup for testing, and testing does not require deep understanding of change detection to properly test form updates and validation. Template-driven forms focus on simple scenarios and are not as reusable. They abstract away the underlying form API, and use [asynchronous data flow](https://angular.dev/#data-flow-in-template-driven-forms) between the view and the data model. The abstraction of template-driven forms also affects testing. Tests are deeply reliant on manual change detection execution to run properly, and require more setup. ## [Setting up the form model](https://angular.dev/#setting-up-the-form-model) Both reactive and template-driven forms track value changes between the form input elements that users interact with and the form data in your component model. The two approaches share underlying building blocks, but differ in how you create and manage the common form-control instances. ### [Common form foundation classes](https://angular.dev/#common-form-foundation-classes) Both reactive and template-driven forms are built on the following base classes. | Base classes | Details | | --- | --- | | `FormControl` | Tracks the value and validation status of an individual form control. | | `FormGroup` | Tracks the same values and status for a collection of form controls. | | `FormArray` | Tracks the same values and status for an array of form controls. | | `ControlValueAccessor` | Creates a bridge between Angular `FormControl` instances and built-in DOM elements. | ### [Setup in reactive forms](https://angular.dev/#setup-in-reactive-forms) With reactive forms, you define the form model directly in the component class. The `[formControl]` directive links the explicitly created `FormControl` instance to a specific form element in the view, using an internal value accessor. The following component implements an input field for a single control, using reactive forms. In this example, the form model is the `FormControl` instance. import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-reactive-favorite-color', template: ` Favorite Color: <input type="text" [formControl]="favoriteColorControl"> `, imports: [ReactiveFormsModule],})export class FavoriteColorReactiveComponent { favoriteColorControl = new FormControl('');} **IMPORTANT:** In reactive forms, the form model is the source of truth; it provides the value and status of the form element at any given point in time, through the `[formControl]` directive on the `<input>` element. ### [Setup in template-driven forms](https://angular.dev/#setup-in-template-driven-forms) In template-driven forms, the form model is implicit, rather than explicit. The directive `NgModel` creates and manages a `FormControl` instance for a given form element. The following component implements the same input field for a single control, using template-driven forms. import {Component} from '@angular/core';import {FormsModule} from '@angular/forms';@Component({ selector: 'app-template-favorite-color', template: ` Favorite Color: <input type="text" [(ngModel)]="favoriteColor"> `, imports: [FormsModule],})export class FavoriteColorTemplateComponent { favoriteColor = '';} **IMPORTANT:** In a template-driven form the source of truth is the template. The `NgModel` directive automatically manages the `FormControl` instance for you. ## [Data flow in forms](https://angular.dev/#data-flow-in-forms) When an application contains a form, Angular must keep the view in sync with the component model and the component model in sync with the view. As users change values and make selections through the view, the new values must be reflected in the data model. Similarly, when the program logic changes values in the data model, those values must be reflected in the view. Reactive and template-driven forms differ in how they handle data flowing from the user or from programmatic changes. The following diagrams illustrate both kinds of data flow for each type of form, using the favorite-color input field defined above. ### [Data flow in reactive forms](https://angular.dev/#data-flow-in-reactive-forms) In reactive forms each form element in the view is directly linked to the form model (a `FormControl` instance). Updates from the view to the model and from the model to the view are synchronous and do not depend on how the UI is rendered. The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps. 1. The user types a value into the input element, in this case the favorite color _Blue_. 2. The form input element emits an "input" event with the latest value. 3. The `ControlValueAccessor` listening for events on the form input element immediately relays the new value to the `FormControl` instance. 4. The `FormControl` instance emits the new value through the `valueChanges` observable. 5. Any subscribers to the `valueChanges` observable receive the new value. Types in the input box Fires the 'input' event Calls setValue() on the FormControl Fires a 'valueChanges' event to observers User <input> ControlValueAccessor FormControl Observers The model-to-view diagram shows how a programmatic change to the model is propagated to the view through the following steps. 1. The user calls the `favoriteColorControl.setValue()` method, which updates the `FormControl` value. 2. The `FormControl` instance emits the new value through the `valueChanges` observable. 3. Any subscribers to the `valueChanges` observable receive the new value. 4. The control value accessor on the form input element updates the element with the new value. Calls setValue() on the FormControl Notifies the ControlValueAccessor Fires a 'valueChanges' event to observers Updates the value of the <input> User <input> ControlValueAccessor FormControl Observers ### [Data flow in template-driven forms](https://angular.dev/#data-flow-in-template-driven-forms) In template-driven forms, each form element is linked to a directive that manages the form model internally. The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps. 1. The user types _Blue_ into the input element. 2. The input element emits an "input" event with the value _Blue_. 3. The control value accessor attached to the input triggers the `setValue()` method on the `FormControl` instance. 4. The `FormControl` instance emits the new value through the `valueChanges` observable. 5. Any subscribers to the `valueChanges` observable receive the new value. 6. The control value accessor also calls the `NgModel.viewToModelUpdate()` method which emits an `ngModelChange` event. 7. Because the component template uses two-way data binding for the `favoriteColor` property, the `favoriteColor` property in the component is updated to the value emitted by the `ngModelChange` event (_Blue_). Types in the input box Fires the 'input' event Calls setValue() on the FormControl Fires a 'valueChanges' event to observers Calls viewToModelUpdate() Emits an ngModelChange event Updates the value of the two-way bound property User <input> ControlValueAccessor FormControl NgModel Observers Component Two-way binding The model-to-view diagram shows how data flows from model to view when the `favoriteColor` changes from _Blue_ to _Red_, through the following steps 1. The `favoriteColor` value is updated in the component. 2. Change detection begins. 3. During change detection, the `ngOnChanges` lifecycle hook is called on the `NgModel` directive instance because the value of one of its inputs has changed. 4. The `ngOnChanges()` method queues an async task to set the value for the internal `FormControl` instance. 5. Change detection completes. 6. On the next tick, the task to set the `FormControl` instance value is executed. 7. The `FormControl` instance emits the latest value through the `valueChanges` observable. 8. Any subscribers to the `valueChanges` observable receive the new value. 9. The control value accessor updates the form input element in the view with the latest `favoriteColor` value. Updates the property value Triggers CD Async actions trigger a second round of Change Detection Second Change Detection Fires a 'valueChanges' event to observers ControlValueAccessor receives valueChanges event Sets the value in the control FormControl Observers ControlValueAccessor <input> First Change Detection Asynchronously sets FormControl value NgModel FormControl Component Property bound to NgModel **NOTE:** `NgModel` triggers a second change detection to avoid `ExpressionChangedAfterItHasBeenChecked` errors, because the value change originates in an input binding. ### [Mutability of the data model](https://angular.dev/#mutability-of-the-data-model) The change-tracking method plays a role in the efficiency of your application. | Forms | Details | | --- | --- | | Reactive forms | Keep the data model pure by providing it as an immutable data structure. Each time a change is triggered on the data model, the `FormControl` instance returns a new data model rather than updating the existing data model. This gives you the ability to track unique changes to the data model through the control's observable. Change detection is more efficient because it only needs to update on unique changes. Because data updates follow reactive patterns, you can integrate with observable operators to transform data. | | Template-driven forms | Rely on mutability with two-way data binding to update the data model in the component as changes are made in the template. Because there are no unique changes to track on the data model when using two-way data binding, change detection is less efficient at determining when updates are required. | The difference is demonstrated in the previous examples that use the favorite-color input element. * With reactive forms, the **`FormControl` instance** always returns a new value when the control's value is updated * With template-driven forms, the **favorite color property** is always modified to its new value ## [Form validation](https://angular.dev/#form-validation) Validation is an integral part of managing any set of forms. Whether you're checking for required fields or querying an external API for an existing username, Angular provides a set of built-in validators as well as the ability to create custom validators. | Forms | Details | | --- | --- | | Reactive forms | Define custom validators as **functions** that receive a control to validate | | Template-driven forms | Tied to template **directives**, and must provide custom validator directives that wrap validation functions | For more information, see [Form Validation](https://angular.dev/guide/forms/form-validation#validating-input-in-reactive-forms). ## [Testing](https://angular.dev/#testing) Testing plays a large part in complex applications. A simpler testing strategy is useful when validating that your forms function correctly. Reactive forms and template-driven forms have different levels of reliance on rendering the UI to perform assertions based on form control and form field changes. The following examples demonstrate the process of testing forms with reactive and template-driven forms. ### [Testing reactive forms](https://angular.dev/#testing-reactive-forms) Reactive forms provide a relatively straightforward testing strategy because they provide synchronous access to the form and data models, and they can be tested without rendering the UI. In these tests, status and data are queried and manipulated through the control without interacting with the change detection cycle. The following tests use the favorite-color components from previous examples to verify the view-to-model and model-to-view data flows for a reactive form. #### [Verifying view-to-model data flow](https://angular.dev/#verifying-view-to-model-data-flow) The first example performs the following steps to verify the view-to-model data flow. 1. Query the view for the form input element, and create a custom "input" event for the test. 2. Set the new value for the input to _Red_, and dispatch the "input" event on the form input element. 3. Assert that the component's `favoriteColorControl` value matches the value from the input. import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {createNewEvent} from '../../shared/utils';import {FavoriteColorReactiveComponent} from './favorite-color.component';describe('Favorite Color Component', () => { let component: FavoriteColorReactiveComponent; let fixture: ComponentFixture<FavoriteColorReactiveComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [FavoriteColorReactiveComponent], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FavoriteColorReactiveComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should update the value of the input field', () => { const input = fixture.nativeElement.querySelector('input'); const event = createNewEvent('input'); input.value = 'Red'; input.dispatchEvent(event); expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red'); }); it('should update the value in the control', () => { component.favoriteColorControl.setValue('Blue'); const input = fixture.nativeElement.querySelector('input'); expect(input.value).toBe('Blue'); });}); The next example performs the following steps to verify the model-to-view data flow. 1. Use the `favoriteColorControl`, a `FormControl` instance, to set the new value. 2. Query the view for the form input element. 3. Assert that the new value set on the control matches the value in the input. import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {createNewEvent} from '../../shared/utils';import {FavoriteColorReactiveComponent} from './favorite-color.component';describe('Favorite Color Component', () => { let component: FavoriteColorReactiveComponent; let fixture: ComponentFixture<FavoriteColorReactiveComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [FavoriteColorReactiveComponent], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FavoriteColorReactiveComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should update the value of the input field', () => { const input = fixture.nativeElement.querySelector('input'); const event = createNewEvent('input'); input.value = 'Red'; input.dispatchEvent(event); expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red'); }); it('should update the value in the control', () => { component.favoriteColorControl.setValue('Blue'); const input = fixture.nativeElement.querySelector('input'); expect(input.value).toBe('Blue'); });}); ### [Testing template-driven forms](https://angular.dev/#testing-template-driven-forms) Writing tests with template-driven forms requires a detailed knowledge of the change detection process and an understanding of how directives run on each cycle to ensure that elements are queried, tested, or changed at the correct time. The following tests use the favorite color components mentioned earlier to verify the data flows from view to model and model to view for a template-driven form. The following test verifies the data flow from view to model. import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {createNewEvent} from '../../shared/utils';import {FavoriteColorTemplateComponent} from './favorite-color.component';describe('FavoriteColorComponent', () => { let component: FavoriteColorTemplateComponent; let fixture: ComponentFixture<FavoriteColorTemplateComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [FavoriteColorTemplateComponent], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FavoriteColorTemplateComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should update the favorite color on the input field', fakeAsync(() => { component.favoriteColor = 'Blue'; fixture.detectChanges(); tick(); const input = fixture.nativeElement.querySelector('input'); expect(input.value).toBe('Blue'); })); it('should update the favorite color in the component', fakeAsync(() => { const input = fixture.nativeElement.querySelector('input'); const event = createNewEvent('input'); input.value = 'Red'; input.dispatchEvent(event); fixture.detectChanges(); expect(component.favoriteColor).toEqual('Red'); }));}); Here are the steps performed in the view to model test. 1. Query the view for the form input element, and create a custom "input" event for the test. 2. Set the new value for the input to _Red_, and dispatch the "input" event on the form input element. 3. Run change detection through the test fixture. 4. Assert that the component `favoriteColor` property value matches the value from the input. The following test verifies the data flow from model to view. import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {createNewEvent} from '../../shared/utils';import {FavoriteColorTemplateComponent} from './favorite-color.component';describe('FavoriteColorComponent', () => { let component: FavoriteColorTemplateComponent; let fixture: ComponentFixture<FavoriteColorTemplateComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [FavoriteColorTemplateComponent], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FavoriteColorTemplateComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should update the favorite color on the input field', fakeAsync(() => { component.favoriteColor = 'Blue'; fixture.detectChanges(); tick(); const input = fixture.nativeElement.querySelector('input'); expect(input.value).toBe('Blue'); })); it('should update the favorite color in the component', fakeAsync(() => { const input = fixture.nativeElement.querySelector('input'); const event = createNewEvent('input'); input.value = 'Red'; input.dispatchEvent(event); fixture.detectChanges(); expect(component.favoriteColor).toEqual('Red'); }));}); Here are the steps performed in the model to view test. 1. Use the component instance to set the value of the `favoriteColor` property. 2. Run change detection through the test fixture. 3. Use the `tick()` method to simulate the passage of time within the `fakeAsync()` task. 4. Query the view for the form input element. 5. Assert that the input value matches the value of the `favoriteColor` property in the component instance. ## [Next steps](https://angular.dev/#next-steps) To learn more about reactive forms, see the following guides: [Reactive forms](https://angular.dev/guide/forms/reactive-forms) [Form validation](https://angular.dev/guide/forms/form-validation#validating-input-in-reactive-forms) [Dynamic forms](https://angular.dev/guide/forms/dynamic-forms) To learn more about template-driven forms, see the following guides: [Template Driven Forms tutorial](https://angular.dev/guide/forms/template-driven-forms) [Form validation](https://angular.dev/guide/forms/form-validation#validating-input-in-template-driven-forms) [NgForm directive API reference](https://angular.dev/api/forms/NgForm) --- ## Page: https://angular.dev/guide/forms/reactive-forms Reactive forms provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a basic form control, 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](https://angular.dev/#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](https://angular.dev/guide/forms/template-driven-forms) in distinct ways. Reactive forms provide synchronous access to the data model, immutability with observable operators, and change tracking through observable streams. Template-driven forms 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](https://angular.dev/guide/forms) for detailed comparisons between the two paradigms. ## [Adding a basic form control](https://angular.dev/#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. 1. ### [Generate a new component and import the ReactiveFormsModule](https://angular.dev/#generate-a-new-component-and-import-the-reactiveformsmodule) Use the CLI command `ng generate component` to generate a component in your project and import `ReactiveFormsModule` from the `@angular/forms` package and add it to your Component's `imports` array. import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }} 2. ### [Declare a FormControl instance](https://angular.dev/#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. import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }} 3. ### [Register the control in the template](https://angular.dev/#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`. <label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button> Using the template binding syntax, the form control is now registered to the `name` input element in the template. The form control and DOM element communicate with each other: the view reflects changes in the model, and the model reflects changes in the view. 4. ### [Display the component](https://angular.dev/#display-the-component) The `FormControl` assigned to the `name` property is displayed when the `<app-name-editor>` component is added to a template. <h1>Reactive Forms</h1><app-name-editor /><app-profile-editor /> ### [Displaying a form control value](https://angular.dev/#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. <label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button> 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](https://angular.dev/api/forms/AbstractControl "API") class are used to control form state and determine when to display messages when handling [input validation](https://angular.dev/#validating-form-input "Learn"). Read about other `FormControl` properties and methods in the [API Reference](https://angular.dev/api/forms/FormControl "Detailed"). ### [Replacing a form control value](https://angular.dev/#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. import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }} Update the template with a button to simulate a name update. When you click the **Update Name** button, the value entered in the form control element is reflected as its current value. <label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button> The form model is the source of truth for the control, so when you click the button, the value of the input is changed within the component class, overriding its current value. **HELPFUL:** In this example, you're using a single control. When using the `setValue()` method with a [form group](https://angular.dev/#grouping-form-controls) or [form array](https://angular.dev/#creating-dynamic-forms) instance, the value needs to match the structure of the group or array. ## [Grouping form controls](https://angular.dev/#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](https://angular.dev/#creating-nested-form-groups "See") 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](https://angular.dev/#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. ng generate component ProfileEditor import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }} 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. 1. ### [Create a FormGroup instance](https://angular.dev/#create-a-formgroup-instance) Create a property in the component class named `profileForm` and set the property to a new form group instance. To initialize the form group, provide the constructor with an object of named keys mapped to their control. For the profile form, add two form control instances with the names `firstName` and `lastName` import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }} 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. 2. ### [Associate the FormGroup model and view](https://angular.dev/#associate-the-formgroup-model-and-view) A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view. <form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button> 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. 3. ### [Save form data](https://angular.dev/#save-form-data) The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The `FormGroup` 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. <form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button> 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. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} 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. <form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button> The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](https://angular.dev/#validating-form-input) section. 4. ### [Display the component](https://angular.dev/#display-the-component-1) To display the `ProfileEditor` component that contains the form, add it to a component template. <h1>Reactive Forms</h1><app-name-editor /><app-profile-editor /> `ProfileEditor` lets you manage the form control instances for the `firstName` and `lastName` controls within the form group instance. ### [Creating nested form groups](https://angular.dev/#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. import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }} 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. 5. ### [Group the nested form in the template](https://angular.dev/#group-the-nested-form-in-the-template) After you update the model in the component class, update the template to connect the form group instance and its input elements. Add the `address` form group containing the `street`, `city`, `state`, and `zip` fields to the `ProfileEditor` template. <form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button> 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](https://angular.dev/#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. import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }} Simulate an update by adding a button to the template to update the user profile on demand. <form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button> When a user clicks the button, the `profileForm` model is updated with new values for `firstName` and `street`. Notice that `street` is provided in an object inside the `address` property. This is necessary because the `patchValue()` method applies the update against the model structure. `PatchValue()` only updates properties that the form model defines. ## [Using the FormBuilder service to generate controls](https://angular.dev/#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. 1. ### [Import the FormBuilder class](https://angular.dev/#import-the-formbuilder-class) Import the `FormBuilder` class from the `@angular/forms` package. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }} 2. ### [Inject the FormBuilder service](https://angular.dev/#inject-the-formbuilder-service) The `FormBuilder` service is an injectable provider from the reactive forms module. Use the `inject()` function to inject this dependency in your component. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }} 3. ### [Generate form controls](https://angular.dev/#generate-form-controls) 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. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }} 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. import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }} import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }} ## [Validating form input](https://angular.dev/#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](https://angular.dev/guide/forms/form-validation) guide. Use the following steps to add form validation. 1. Import a validator function in your form component. 2. Add the validator to the field in the form. 3. Add logic to handle the validation status. The most common validation is making a field required. The following example shows how to add a required validation to the `firstName` control and display the result of validation. 1. ### [Import a validator function](https://angular.dev/#import-a-validator-function) Reactive forms include a set of validator functions for common use cases. These functions receive a control to validate against and return an error object or a null value based on the validation check. Import the `Validators` class from the `@angular/forms` package. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} 2. ### [Make a field required](https://angular.dev/#make-a-field-required) In the `ProfileEditor` component, add the `Validators.required` static method as the second item in the array for the `firstName` control. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} 3. ### [Display form status](https://angular.dev/#display-form-status) When you add a required field to the form control, its initial status is invalid. This invalid status propagates to the parent form group element, making its status invalid. Access the current status of the form group instance through its `status` property. Display the current status of `profileForm` using interpolation. <form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button> 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](https://angular.dev/guide/forms/form-validation) guide. ## [Creating dynamic forms](https://angular.dev/#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`. 1. ### [Import the `FormArray` class](https://angular.dev/#import-the-formarray-class) Import the `FormArray` class from `@angular/forms` to use for type information. The `FormBuilder` service is ready to create a `FormArray` instance. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }} 2. ### [Define a `FormArray` control](https://angular.dev/#define-a-formarray-control) You can initialize a form array with any number of controls, from zero to many, by defining them in an array. Add an `aliases` property to the form group instance for `profileForm` to define the form array. Use the `FormBuilder.array()` method to define the array, and the `FormBuilder.control()` method to populate the array with an initial control. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} The aliases control in the form group instance is now populated with a single control until more controls are added dynamically. 3. ### [Access the `FormArray` control](https://angular.dev/#access-the-formarray-control) A getter provides access to the aliases in the form array instance compared to repeating the `profileForm.get()` method to get each instance. The form array instance represents an undefined number of controls in an array. It's convenient to access a control through a getter, and this approach is straightforward to repeat for additional controls. Use the getter syntax to create an `aliases` class property to retrieve the alias's form array control from the parent form group. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} 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. import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }} In the template, each control is displayed as a separate input field. 4. ### [Display the form array in the template](https://angular.dev/#display-the-form-array-in-the-template) To attach the aliases from your form model, you must add it to the template. Similar to the `formGroupName` 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. <form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button> The `*ngFor` directive 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. 5. ### [Add an alias](https://angular.dev/#add-an-alias) Initially, the form contains one `Alias` field. To add another field, click the **Add Alias** button. You can also validate the array of aliases reported by the form model displayed by `Form Value` at the bottom of the template. Instead of a form control instance for each alias, you can compose another form group instance with additional fields. The process of defining a control for each item is the same. ## [Reactive forms API summary](https://angular.dev/#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](https://angular.dev/api#forms "API"). ### [Classes](https://angular.dev/#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](https://angular.dev/#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. | --- ## Page: https://angular.dev/guide/forms/typed-forms As of Angular 14, reactive forms are strictly typed by default. As background for this guide, you should already be familiar with [Angular Reactive Forms](https://angular.dev/guide/forms/reactive-forms). ## [Overview of Typed Forms](https://angular.dev/#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](https://angular.dev/api/forms/FormGroup).) In previous Angular versions, most of these APIs included `any` somewhere in their types, and interacting with the structure of the controls, or the values themselves, was not type-safe. For example: you could write the following invalid code: const emailDomain = login.value.email.domain; With strictly typed reactive forms, the above code does not compile, because there is no `domain` property on `email`. In addition to the added safety, the types enable a variety of other improvements, such as better autocomplete in IDEs, and an explicit way to specify form structure. These improvements currently apply only to _reactive_ forms (not [_template-driven_ forms](https://angular.dev/guide/forms/template-driven-forms)). ## [Untyped Forms](https://angular.dev/#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](https://angular.dev/#formcontrol-getting-started) The simplest possible form consists of a single control: const email = new FormControl('angularrox@gmail.com'); This control will be automatically inferred to have the type `FormControl<string|null>`. TypeScript will automatically enforce this type throughout the [`FormControl` API](https://angular.dev/api/forms/FormControl), such as `email.value`, `email.valueChanges`, `email.setValue(...)`, etc. ### [Nullability](https://angular.dev/#nullability) You might wonder: why does the type of this control include `null`? This is because the control can become `null` at any time, by calling reset: const email = new FormControl('angularrox@gmail.com');email.reset();console.log(email.value); // null TypeScript will enforce that you always handle the possibility that the control has become `null`. If you want to make this control non-nullable, you may use the `nonNullable` option. This will cause the control to reset to its initial value, instead of `null`: const email = new FormControl('angularrox@gmail.com', {nonNullable: true});email.reset();console.log(email.value); // angularrox@gmail.com To reiterate, this option affects the runtime behavior of your form when `.reset()` is called, and should be flipped with care. ### [Specifying an Explicit Type](https://angular.dev/#specifying-an-explicit-type) It is possible to specify the type, instead of relying on inference. Consider a control that is initialized to `null`. Because the initial value is `null`, TypeScript will infer `FormControl<null>`, which is narrower than we want. const email = new FormControl(null);email.setValue('angularrox@gmail.com'); // Error! To prevent this, we explicitly specify the type as `string|null`: const email = new FormControl<string|null>(null);email.setValue('angularrox@gmail.com'); ## [`FormArray`: Dynamic, Homogenous Collections](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/api/forms/FormGroup). Any disabled control will not appear in the group's value. As a consequence, the type of `login.value` is `Partial<{email: string, password: string}>`. The `Partial` in this type means that each member might be undefined. More specifically, the type of `login.value.email` is `string|undefined`, and TypeScript will enforce that you handle the possibly `undefined` value (if you have `strictNullChecks` enabled). If you want to access the value _including_ disabled controls, and thus bypass possible `undefined` fields, you can use `login.getRawValue()`. ### [Optional Controls and Dynamic Groups](https://angular.dev/#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`](https://angular.dev/#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`](https://angular.dev/#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`. --- ## Page: https://angular.dev/guide/forms/template-driven-forms This tutorial shows you how to create a template-driven form. The control elements in the form are bound to data properties that have input validation. The input validation helps maintain data integrity and styling to improve the user experience. Template-driven forms use [two-way data binding](https://angular.dev/guide/templates/two-way-binding) to update the data model in the component as changes are made in the template and vice versa. ### Template vs Reactive forms Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template. Reactive forms provide a model-driven approach to building forms. Template-driven forms are a great choice for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see [Choosing an approach](https://angular.dev/guide/forms#choosing-an-approach) You can build almost any kind of form with an Angular template —login forms, contact forms, and pretty much any business form. You can lay out the controls creatively and bind them to the data in your object model. You can specify validation rules and display validation errors, conditionally allow input from specific controls, trigger built-in visual feedback, and much more. ## [Objectives](https://angular.dev/#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](https://angular.dev/guide/templates/variables#template-reference-variables) ## [Build a template-driven form](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/api/forms/NgForm#properties) 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](https://angular.dev/#build-the-form) 1. The provided sample application creates the `Actor` class which defines the data model reflected in the form. export class Actor { constructor( public id: number, public name: string, public skill: string, public studio?: string, ) {}} 1. The form layout and details are defined in the `ActorFormComponent` class. import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} 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. import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} 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. import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} 4. The form is displayed in the application layout defined by the root component's template. <app-actor-form /> The initial template defines the layout for a form with two form groups and a submit button. The form groups correspond to two properties of the Actor data model, name and studio. Each group has a label and a box for user input. * The **Name** `<input>` control element has the HTML5 `required` attribute * The **Studio** `<input>` control element does not because `studio` is optional The **Submit** button has some classes on it for styling. At this point, the form layout is all plain HTML5, with no bindings or directives. 5. The sample form uses some style classes from [Twitter Bootstrap](https://getbootstrap.com/css): `container`, `form-group`, `form-control`, and `btn`. To use these styles, the application's style sheet imports the library. @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css'); 1. The form requires that an actor's skill is chosen from a predefined list of `skills` maintained internally in `ActorFormComponent`. The Angular [NgForOf directive](https://angular.dev/api/common/NgForOf "API") iterates over the data values to populate the `<select>` element. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> If you run the application right now, you see the list of skills in the selection control. The input elements are not yet bound to data values or events, so they are still blank and have no behavior. ## [Bind input controls to data properties](https://angular.dev/#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)]="..."`. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> **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](https://angular.dev/#access-the-overall-form-status) When you imported the `FormsModule` in your component, Angular automatically created and attached an [NgForm](https://angular.dev/api/forms/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](https://angular.dev/guide/templates/variables#template-reference-variables). 1. Edit the template file `actor-form.component.html`. 2. Update the `<form>` tag with a template reference variable, `#actorForm`, and set its value as follows. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 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](https://angular.dev/#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`](https://angular.dev/api/common/JsonPipe) pipe at the top to the component's template, which serializes the data to a string. After these revisions, the form template should look like the following: <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> You'll notice that: * Each `<input>` element has an `id` property. This is used by the `<label>` element's `for` attribute to match the label to its input control. This is a [standard HTML feature](https://developer.mozilla.org/docs/Web/HTML/Element/label). * Each `<input>` element also has the required `name` property that Angular uses to register the control with the form. When you have observed the effects, you can delete the `{{ model | json }}` text binding. ## [Track form states](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#observe-control-states) To see how the classes are added and removed by the framework, open the browser's developer tools and inspect the `<input>` element that represents the actor name. 1. Using your browser's developer tools, find the `<input>` element that corresponds to the **Name** input box. You can see that the element has multiple CSS classes in addition to "form-control". 2. When you first bring it up, the classes indicate that it has a valid value, that the value has not been changed since initialization or reset, and that the control has not been visited since initialization or reset. <input class="form-control ng-untouched ng-pristine ng-valid">; 3. Take the following actions on the **Name** `<input>` box, and observe which classes appear. * Look but don't touch. The classes indicate that it is untouched, pristine, and valid. * Click inside the name box, then click outside it. The control has now been visited, and the element has the `ng-touched` class instead of the `ng-untouched` class. * Add slashes to the end of the name. It is now touched and dirty. * Erase the name. This makes the value invalid, so the `ng-invalid` class replaces the `ng-valid` class. ### [Create visual feedback for states](https://angular.dev/#create-visual-feedback-for-states) The `ng-valid`/`ng-invalid` pair is particularly interesting, because you want to send a strong visual signal when the values are invalid. You also want to mark required fields. You can mark required fields and invalid data at the same time with a colored bar on the left of the input box. To change the appearance in this way, take the following steps. 1. Add definitions for the `ng-*` CSS classes. 2. Add these class definitions to a new `forms.css` file. 3. Add the new file to the project as a sibling to `index.html`: .ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */} 1. In the `index.html` file, update the `<head>` tag to include the new style sheet. <!DOCTYPE html><html lang="en"> <head> <title>Hero Form</title> <base href="/"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/forms.css"> </head> <body> <app-root></app-root> </body></html> ### [Show and hide validation error messages](https://angular.dev/#show-and-hide-validation-error-messages) The **Name** input box is required and clearing it turns the bar red. That indicates that something is wrong, but the user doesn't know what is wrong or what to do about it. You can provide a helpful message by checking for and responding to the control's state. The **Skill** select box is also required, but it doesn't need this kind of error handling because the selection box already constrains the selection to valid values. To define and show an error message when appropriate, take the following steps. 1. ### [Add a local reference to the input](https://angular.dev/#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`](https://angular.dev/api/core/Directive#exportAs) property. This property tells Angular how to link a reference variable to a directive. 2. ### [Add the error message](https://angular.dev/#add-the-error-message) Add a `<div>` that contains a suitable error message. 3. ### [Make the error message conditional](https://angular.dev/#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. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 5. ### [Add a conditional error message to name](https://angular.dev/#add-a-conditional-error-message-to-name) Add a conditional error message to the `name` input box, as in the following example. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 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](https://angular.dev/#add-a-new-actor) This exercise shows how you can respond to a native HTML button-click event by adding to the model data. To let form users add a new actor, you will add a **New Actor** button that responds to a click event. 1. In the template, place a "New Actor" `<button>` element at the bottom of the form. 2. In the component file, add the actor-creation method to the actor data model. import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} 1. Bind the button's click event to an actor-creation method, `newActor()`. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 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. 2. 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**. 3. 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. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> Now clicking **New Actor** resets both the form and its control flags. ## [Submit the form with `ngSubmit`](https://angular.dev/#submit-the-form-with-ngsubmit) The user should be able to submit this form after filling it in. The **Submit** button at the bottom of the form does nothing on its own, but it does trigger a form-submit event because of its type (`type="submit"`). To respond to this event, take the following steps. 1. ### [Listen to ngOnSubmit](https://angular.dev/#listen-to-ngonsubmit) Bind the form's [`ngSubmit`](https://angular.dev/api/forms/NgForm#properties) event property to the actor-form component's `onSubmit()` method. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 2. ### [Bind the disabled property](https://angular.dev/#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. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> 3. ### [Run the application](https://angular.dev/#run-the-application) Notice that the button is enabled —although it doesn't do anything useful yet. 4. ### [Delete the Name value](https://angular.dev/#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. ### [Respond to form submission](https://angular.dev/#respond-to-form-submission) To show a response to form submission, you can hide the data entry area and display something else in its place. 1. ### [Wrap the form](https://angular.dev/#wrap-the-form) Wrap the entire form in a `<div>` and bind its `hidden` property to the `ActorFormComponent.submitted` property. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</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: import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} When you click the **Submit** button, the `submitted` flag becomes true and the form disappears. 2. ### [Add the submitted state](https://angular.dev/#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. <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> This `<div>`, which shows a read-only actor with interpolation bindings, appears only while the component is in the submitted state. The alternative display includes an _Edit_ button whose click event is bound to an expression that clears the `submitted` flag. 3. ### [Test the Edit button](https://angular.dev/#test-the-edit-button) Click the _Edit_ button to switch the display back to the editable form. ## [Summary](https://angular.dev/#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: import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////} <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div> export class Actor { constructor( public id: number, public name: string, public skill: string, public studio?: string, ) {}} <app-actor-form /> import {Component} from '@angular/core';import {ActorFormComponent} from './actor-form/actor-form.component';@Component({ selector: 'app-root', templateUrl: './app.component.html', imports: [ActorFormComponent],})export class AppComponent {} import {bootstrapApplication} from '@angular/platform-browser';import {AppComponent} from './app/app.component';import {provideZoneChangeDetection} from '@angular/core';bootstrapApplication(AppComponent, { providers: [provideZoneChangeDetection({eventCoalescing: true})],}).catch((err) => console.error(err)); .ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */} --- ## Page: https://angular.dev/guide/forms/form-validation You can improve overall data quality by validating user input for accuracy and completeness. This page shows how to validate user input from the UI and display useful validation messages, in both reactive and template-driven forms. ## [Validating input in template-driven forms](https://angular.dev/#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`: <div> <h2>Template-Driven Form</h2> <form #actorForm="ngForm" appUnambiguousRole> <div [hidden]="actorForm.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel"> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" name="role" #role="ngModel" [(ngModel)]="actor.role" [ngModelOptions]="{ updateOn: 'blur' }" appUniqueRole> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert"> Name cannot match role. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" name="skill" required [(ngModel)]="actor.skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.errors && skill.touched) { <div class="alert"> @if (skill.errors['required']) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" [disabled]="actorForm.invalid">Submit </button> <button type="button" (click)="actorForm.resetForm({})">Reset </button> </div> @if (actorForm.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="actorForm.resetForm({})">Add new actor</button> </div> } </form></div> 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](https://angular.dev/#defining-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](https://angular.dev/api/forms/AbstractControl) API reference. * The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if the `name` is invalid and the control is either `dirty` or `touched`. * Each nested `<div>` 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#built-in-validator-functions) You can choose to [write your own validator functions](https://angular.dev/#defining-custom-validators), or you can use some of Angular's built-in validators. The same built-in validators that are available as attributes in template-driven forms, such as `required` and `minlength`, are all available to use as functions from the `Validators` class. For a full list of built-in validators, see the [Validators](https://angular.dev/api/forms/Validators) API reference. To update the actor form to be a reactive form, use some of the same built-in validators —this time, in function form, as in the following example. import {Component} from '@angular/core';import {FormControl, FormGroup, Validators, ReactiveFormsModule} from '@angular/forms';import {forbiddenNameValidator} from '../shared/forbidden-name.directive';@Component({ selector: 'app-actor-form-reactive', templateUrl: './actor-form-reactive.component.html', styleUrls: ['./actor-form-reactive.component.css'], imports: [ReactiveFormsModule],})export class HeroFormReactiveComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; actor = {name: 'Tom Cruise', role: 'Romeo', skill: this.skills[3]}; actorForm: FormGroup = new FormGroup({ name: new FormControl(this.actor.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator. ]), role: new FormControl(this.actor.role), skill: new FormControl(this.actor.skill, Validators.required), }); get name() { return this.actorForm.get('name'); } get skill() { return this.actorForm.get('skill'); }} In this example, the `name` control sets up two built-in validators —`Validators.required` 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. <div class="container"> <h2>Reactive Form</h2> <form [formGroup]="actorForm" #formDir="ngForm"> <div [hidden]="formDir.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name" required> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert alert-danger"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" class="form-control" formControlName="role"> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert alert-danger role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert alert-danger"> Name cannot match role or audiences will be confused. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" class="form-control" formControlName="skill" required> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.invalid && skill.touched) { <div class="alert alert-danger"> @if (skill.hasError('required')) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" class="btn btn-default" [disabled]="actorForm.invalid">Submit </button> <button type="button" class="btn btn-default" (click)="formDir.resetForm({})">Reset </button> </div> </form> @if (formDir.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="formDir.resetForm({})">Add new actor</button> </div> }</div> This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class. Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained for accessibility purposes. ## [Defining custom validators](https://angular.dev/#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. import {Directive, input} from '@angular/core';import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn,} from '@angular/forms';/** An actor's name can't match the given regular expression */export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? {forbiddenName: {value: control.value}} : null; };}@Directive({ selector: '[appForbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}],})export class ForbiddenValidatorDirective implements Validator { forbiddenName = input<string>('', {alias: 'appForbiddenName'}); validate(control: AbstractControl): ValidationErrors | null { return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control) : null; }} The function is a factory that takes a regular expression to detect a _specific_ forbidden name and returns a validator function. In this sample, the forbidden name is "bob", so the validator rejects any actor name containing "bob". Elsewhere it could reject "alice" or any name that the configuring regular expression matches. The `forbiddenNameValidator` factory returns the configured validator function. That function takes an Angular control object and returns _either_ null if the control value is valid _or_ a validation error object. The validation error object typically has a property whose name is the validation key, `'forbiddenName'`, and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`. Custom async validators are similar to sync validators, but they must instead return a Promise or observable that later emits null or a validation error object. In the case of an observable, the observable must complete, at which point the form uses the last value emitted for validation. ### [Adding custom validators to reactive forms](https://angular.dev/#adding-custom-validators-to-reactive-forms) In reactive forms, add a custom validator by passing the function directly to the `FormControl`. import {Component} from '@angular/core';import {FormControl, FormGroup, Validators, ReactiveFormsModule} from '@angular/forms';import {forbiddenNameValidator} from '../shared/forbidden-name.directive';@Component({ selector: 'app-actor-form-reactive', templateUrl: './actor-form-reactive.component.html', styleUrls: ['./actor-form-reactive.component.css'], imports: [ReactiveFormsModule],})export class HeroFormReactiveComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; actor = {name: 'Tom Cruise', role: 'Romeo', skill: this.skills[3]}; actorForm: FormGroup = new FormGroup({ name: new FormControl(this.actor.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator. ]), role: new FormControl(this.actor.role), skill: new FormControl(this.actor.skill, Validators.required), }); get name() { return this.actorForm.get('name'); } get skill() { return this.actorForm.get('skill'); }} ### [Adding custom validators to template-driven forms](https://angular.dev/#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. import {Directive, input} from '@angular/core';import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn,} from '@angular/forms';/** An actor's name can't match the given regular expression */export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? {forbiddenName: {value: control.value}} : null; };}@Directive({ selector: '[appForbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}],})export class ForbiddenValidatorDirective implements Validator { forbiddenName = input<string>('', {alias: 'appForbiddenName'}); validate(control: AbstractControl): ValidationErrors | null { return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control) : null; }} The directive class then implements the `Validator` interface, so that it can easily integrate with Angular forms. Here is the rest of the directive to help you get an idea of how it all comes together. import {Directive, input} from '@angular/core';import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn,} from '@angular/forms';/** An actor's name can't match the given regular expression */export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? {forbiddenName: {value: control.value}} : null; };}@Directive({ selector: '[appForbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}],})export class ForbiddenValidatorDirective implements Validator { forbiddenName = input<string>('', {alias: 'appForbiddenName'}); validate(control: AbstractControl): ValidationErrors | null { return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control) : null; }} Once the `ForbiddenValidatorDirective` is ready, you can add its selector, `appForbiddenName`, to any input element to activate it. For example: <div> <h2>Template-Driven Form</h2> <form #actorForm="ngForm" appUnambiguousRole> <div [hidden]="actorForm.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel"> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" name="role" #role="ngModel" [(ngModel)]="actor.role" [ngModelOptions]="{ updateOn: 'blur' }" appUniqueRole> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert"> Name cannot match role. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" name="skill" required [(ngModel)]="actor.skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.errors && skill.touched) { <div class="alert"> @if (skill.errors['required']) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" [disabled]="actorForm.invalid">Submit </button> <button type="button" (click)="actorForm.resetForm({})">Reset </button> </div> @if (actorForm.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="actorForm.resetForm({})">Add new actor</button> </div> } </form></div> **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](https://angular.dev/#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. .ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */}.alert div { background-color: #fed3d3; color: #820000; padding: 1rem; margin-bottom: 1rem;}.form-group { margin-bottom: 1rem;}label { display: block; margin-bottom: .5rem;}select { width: 100%; padding: .5rem;} ## [Cross-field validation](https://angular.dev/#cross-field-validation) A cross-field validator is a [custom validator](https://angular.dev/#defining-custom-validators "Read") 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](https://angular.dev/#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. const actorForm = new FormGroup({ 'name': new FormControl(), 'role': new FormControl(), 'skill': new FormControl()}, { validators: unambiguousRoleValidator }); The validator code is as follows. import {Directive} from '@angular/core';import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn,} from '@angular/forms';/** An actor's name can't match the actor's role */export const unambiguousRoleValidator: ValidatorFn = ( control: AbstractControl,): ValidationErrors | null => { const name = control.get('name'); const role = control.get('role'); return name && role && name.value === role.value ? {unambiguousRole: true} : null;};@Directive({ selector: '[appUnambiguousRole]', providers: [ {provide: NG_VALIDATORS, useExisting: UnambiguousRoleValidatorDirective, multi: true}, ],})export class UnambiguousRoleValidatorDirective implements Validator { validate(control: AbstractControl): ValidationErrors | null { return unambiguousRoleValidator(control); }} 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](https://angular.dev/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. <div class="container"> <h2>Reactive Form</h2> <form [formGroup]="actorForm" #formDir="ngForm"> <div [hidden]="formDir.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name" required> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert alert-danger"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" class="form-control" formControlName="role"> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert alert-danger role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert alert-danger"> Name cannot match role or audiences will be confused. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" class="form-control" formControlName="skill" required> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.invalid && skill.touched) { <div class="alert alert-danger"> @if (skill.hasError('required')) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" class="btn btn-default" [disabled]="actorForm.invalid">Submit </button> <button type="button" class="btn btn-default" (click)="formDir.resetForm({})">Reset </button> </div> </form> @if (formDir.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="formDir.resetForm({})">Add new actor</button> </div> }</div> This `*ngIf` 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](https://angular.dev/#control-status-css-classes). ### [Adding cross-validation to template-driven forms](https://angular.dev/#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](https://angular.dev/api/forms/NG_VALIDATORS), as shown in the following example. import {Directive} from '@angular/core';import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn,} from '@angular/forms';/** An actor's name can't match the actor's role */export const unambiguousRoleValidator: ValidatorFn = ( control: AbstractControl,): ValidationErrors | null => { const name = control.get('name'); const role = control.get('role'); return name && role && name.value === role.value ? {unambiguousRole: true} : null;};@Directive({ selector: '[appUnambiguousRole]', providers: [ {provide: NG_VALIDATORS, useExisting: UnambiguousRoleValidatorDirective, multi: true}, ],})export class UnambiguousRoleValidatorDirective implements Validator { validate(control: AbstractControl): ValidationErrors | null { return unambiguousRoleValidator(control); }} You must add the new directive to the HTML template. Because the validator must be registered at the highest level in the form, the following template puts the directive on the `form` tag. <div> <h2>Template-Driven Form</h2> <form #actorForm="ngForm" appUnambiguousRole> <div [hidden]="actorForm.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel"> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" name="role" #role="ngModel" [(ngModel)]="actor.role" [ngModelOptions]="{ updateOn: 'blur' }" appUniqueRole> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert"> Name cannot match role. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" name="skill" required [(ngModel)]="actor.skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.errors && skill.touched) { <div class="alert"> @if (skill.errors['required']) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" [disabled]="actorForm.invalid">Submit </button> <button type="button" (click)="actorForm.resetForm({})">Reset </button> </div> @if (actorForm.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="actorForm.resetForm({})">Add new actor</button> </div> } </form></div> To provide better user experience, an appropriate error message appears when the form is invalid. <div> <h2>Template-Driven Form</h2> <form #actorForm="ngForm" appUnambiguousRole> <div [hidden]="actorForm.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel"> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" name="role" #role="ngModel" [(ngModel)]="actor.role" [ngModelOptions]="{ updateOn: 'blur' }" appUniqueRole> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert"> Name cannot match role. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" name="skill" required [(ngModel)]="actor.skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.errors && skill.touched) { <div class="alert"> @if (skill.errors['required']) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" [disabled]="actorForm.invalid">Submit </button> <button type="button" (click)="actorForm.resetForm({})">Reset </button> </div> @if (actorForm.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="actorForm.resetForm({})">Add new actor</button> </div> } </form></div> This is the same in both template-driven and reactive forms. ## [Creating asynchronous validators](https://angular.dev/#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. <input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator><app-spinner *ngIf="model.pending"></app-spinner> ### [Implementing a custom async validator](https://angular.dev/#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. import {Directive, forwardRef, inject, Injectable} from '@angular/core';import { AsyncValidator, AbstractControl, NG_ASYNC_VALIDATORS, ValidationErrors,} from '@angular/forms';import {catchError, map} from 'rxjs/operators';import {ActorsService} from './actors.service';import {Observable, of} from 'rxjs';@Injectable({providedIn: 'root'})export class UniqueRoleValidator implements AsyncValidator { private readonly actorsService = inject(ActorsService); validate(control: AbstractControl): Observable<ValidationErrors | null> { return this.actorsService.isRoleTaken(control.value).pipe( map((isTaken) => (isTaken ? {uniqueRole: true} : null)), catchError(() => of(null)), ); }}@Directive({ selector: '[appUniqueRole]', providers: [ { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => UniqueRoleValidatorDirective), multi: true, }, ],})export class UniqueRoleValidatorDirective implements AsyncValidator { private readonly validator = inject(UniqueRoleValidator); validate(control: AbstractControl): Observable<ValidationErrors | null> { return this.validator.validate(control); }} The `actorsService` property is initialized with an instace of the `ActorsService` token, which defines the following interface. interface ActorsService { isRoleTaken: (role: string) => Observable<boolean>;} In a real world application, the `ActorsService` would be responsible for making an HTTP request to the actor database to check if the role is available. From the validator's point of view, the actual implementation of the service is not important, so the example can just code against the `ActorsService` interface. As the validation begins, the `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](https://angular.dev/#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. import {Component, inject} from '@angular/core';import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';import {forbiddenNameValidator} from '../shared/forbidden-name.directive';import {UniqueRoleValidator} from '../shared/role.directive';@Component({ selector: 'app-actor-form-reactive', templateUrl: './actor-form-reactive.component.html', styleUrls: ['./actor-form-reactive.component.css'], imports: [ReactiveFormsModule],})export class HeroFormReactiveComponent { roleValidator = inject(UniqueRoleValidator); skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; actor = {name: 'Tom Cruise', role: 'Romeo', skill: this.skills[3]}; actorForm!: FormGroup; ngOnInit(): void { const roleControl = new FormControl('', { asyncValidators: [this.roleValidator.validate.bind(this.roleValidator)], updateOn: 'blur', }); roleControl.setValue(this.actor.role); this.actorForm = new FormGroup({ name: new FormControl(this.actor.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i), ]), role: roleControl, skill: new FormControl(this.actor.skill, Validators.required), }); } get name() { return this.actorForm.get('name'); } get skill() { return this.actorForm.get('skill'); } get role() { return this.actorForm.get('role'); }} 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](https://angular.dev/api/forms/AbstractControlOptions) API reference. import {Component, inject} from '@angular/core';import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';import {forbiddenNameValidator} from '../shared/forbidden-name.directive';import {UniqueRoleValidator} from '../shared/role.directive';@Component({ selector: 'app-actor-form-reactive', templateUrl: './actor-form-reactive.component.html', styleUrls: ['./actor-form-reactive.component.css'], imports: [ReactiveFormsModule],})export class HeroFormReactiveComponent { roleValidator = inject(UniqueRoleValidator); skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; actor = {name: 'Tom Cruise', role: 'Romeo', skill: this.skills[3]}; actorForm!: FormGroup; ngOnInit(): void { const roleControl = new FormControl('', { asyncValidators: [this.roleValidator.validate.bind(this.roleValidator)], updateOn: 'blur', }); roleControl.setValue(this.actor.role); this.actorForm = new FormGroup({ name: new FormControl(this.actor.name, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i), ]), role: roleControl, skill: new FormControl(this.actor.skill, Validators.required), }); } get name() { return this.actorForm.get('name'); } get skill() { return this.actorForm.get('skill'); } get role() { return this.actorForm.get('role'); }} ### [Adding async validators to template-driven forms](https://angular.dev/#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. import {Directive, forwardRef, inject, Injectable} from '@angular/core';import { AsyncValidator, AbstractControl, NG_ASYNC_VALIDATORS, ValidationErrors,} from '@angular/forms';import {catchError, map} from 'rxjs/operators';import {ActorsService} from './actors.service';import {Observable, of} from 'rxjs';@Injectable({providedIn: 'root'})export class UniqueRoleValidator implements AsyncValidator { private readonly actorsService = inject(ActorsService); validate(control: AbstractControl): Observable<ValidationErrors | null> { return this.actorsService.isRoleTaken(control.value).pipe( map((isTaken) => (isTaken ? {uniqueRole: true} : null)), catchError(() => of(null)), ); }}@Directive({ selector: '[appUniqueRole]', providers: [ { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => UniqueRoleValidatorDirective), multi: true, }, ],})export class UniqueRoleValidatorDirective implements AsyncValidator { private readonly validator = inject(UniqueRoleValidator); validate(control: AbstractControl): Observable<ValidationErrors | null> { return this.validator.validate(control); }} Then, as with synchronous validators, add the directive's selector to an input to activate it. <div> <h2>Template-Driven Form</h2> <form #actorForm="ngForm" appUnambiguousRole> <div [hidden]="actorForm.submitted"> <div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob" [(ngModel)]="actor.name" #name="ngModel"> @if (name.invalid && (name.dirty || name.touched)) { <div class="alert"> @if (name.hasError('required')) { <div> Name is required. </div> } @if (name.hasError('minlength')) { <div> Name must be at least 4 characters long. </div> } @if (name.hasError('forbiddenName')) { <div> Name cannot be Bob. </div> } </div> } </div> <div class="form-group"> <label for="role">Role</label> <input type="text" id="role" name="role" #role="ngModel" [(ngModel)]="actor.role" [ngModelOptions]="{ updateOn: 'blur' }" appUniqueRole> @if (role.pending) { <div>Validating...</div> } @if (role.invalid) { <div class="alert role-errors"> @if (role.hasError('uniqueRole')) { <div> Role is already taken. </div> } </div> } </div> @if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) { <div class="cross-validation-error-message alert"> Name cannot match role. </div> } </div> <div class="form-group"> <label for="skill">Skill</label> <select id="skill" name="skill" required [(ngModel)]="actor.skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> @if (skill.errors && skill.touched) { <div class="alert"> @if (skill.errors['required']) { <div>Skill is required.</div> } </div> } </div> <p>Complete the form to enable the Submit button.</p> <button type="submit" [disabled]="actorForm.invalid">Submit </button> <button type="button" (click)="actorForm.resetForm({})">Reset </button> </div> @if (actorForm.submitted) { <div class="submitted-message"> <p>You've submitted your actor, {{ actorForm.value.name }}!</p> <button type="button" (click)="actorForm.resetForm({})">Add new actor</button> </div> } </form></div> ### [Optimizing performance of async validators](https://angular.dev/#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. <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](https://angular.dev/#interaction-with-native-html-form-validation) By default, Angular disables [native HTML form validation](https://developer.mozilla.org/docs/Web/Guide/HTML/Constraint_validation) by adding the `novalidate` attribute on the enclosing `<form>` and uses directives to match these attributes with validator functions in the framework. If you want to use native validation **in combination** with Angular-based validation, you can re-enable it with the `ngNativeValidate` directive. See the [API docs](https://angular.dev/api/forms/NgForm#native-dom-validation-ui) for details. --- ## Page: https://angular.dev/guide/forms/dynamic-forms Many forms, such as questionnaires, can be very similar to one another in format and intent. To make it faster and easier to generate different versions of such a form, you can create a _dynamic form template_ based on metadata that describes the business object model. Then, use the template to generate new forms automatically, according to changes in the data model. The technique is particularly useful when you have a type of form whose content must change frequently to meet rapidly changing business and regulatory requirements. A typical use-case is a questionnaire. You might need to get input from users in different contexts. The format and style of the forms a user sees should remain constant, while the actual questions you need to ask vary with the context. In this tutorial you will build a dynamic form that presents a basic questionnaire. You build an online application for heroes seeking employment. The agency is constantly tinkering with the application process, but by using the dynamic form you can create the new forms on the fly without changing the application code. The tutorial walks you through the following steps. 1. Enable reactive forms for a project. 2. Establish a data model to represent form controls. 3. Populate the model with sample data. 4. Develop a component to create form controls dynamically. The form you create uses input validation and styling to improve the user experience. It has a Submit button that is only enabled when all user input is valid, and flags invalid input with color coding and error messages. The basic version can evolve to support a richer variety of questions, more graceful rendering, and superior user experience. ## [Enable reactive forms for your project](https://angular.dev/#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. import {Component, inject, input} from '@angular/core';import {FormGroup, ReactiveFormsModule} from '@angular/forms';import {DynamicFormQuestionComponent} from './dynamic-form-question.component';import {QuestionBase} from './question-base';import {QuestionControlService} from './question-control.service';@Component({ selector: 'app-dynamic-form', templateUrl: './dynamic-form.component.html', providers: [QuestionControlService], imports: [DynamicFormQuestionComponent, ReactiveFormsModule],})export class DynamicFormComponent { private readonly qcs = inject(QuestionControlService); questions = input<QuestionBase<string>[] | null>([]); form: FormGroup = this.qcs.toFormGroup(this.questions() as QuestionBase<string>[]); payLoad = ''; onSubmit() { this.payLoad = JSON.stringify(this.form.getRawValue()); }} import {Component, input, Input} from '@angular/core';import {FormGroup, ReactiveFormsModule} from '@angular/forms';import {QuestionBase} from './question-base';@Component({ selector: 'app-question', templateUrl: './dynamic-form-question.component.html', imports: [ReactiveFormsModule],})export class DynamicFormQuestionComponent { question = input.required<QuestionBase<string>>(); form = input.required<FormGroup>(); get isValid() { return this.form().controls[this.question().key].valid; }} ## [Create a form object model](https://angular.dev/#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. export class QuestionBase<T> { value: T | undefined; key: string; label: string; required: boolean; order: number; controlType: string; type: string; options: {key: string; value: string}[]; constructor( options: { value?: T; key?: string; label?: string; required?: boolean; order?: number; controlType?: string; type?: string; options?: {key: string; value: string}[]; } = {}, ) { this.value = options.value; this.key = options.key || ''; this.label = options.label || ''; this.required = !!options.required; this.order = options.order === undefined ? 1 : options.order; this.controlType = options.controlType || ''; this.type = options.type || ''; this.options = options.options || []; }} ### [Define control classes](https://angular.dev/#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`). import {QuestionBase} from './question-base';export class TextboxQuestion extends QuestionBase<string> { override controlType = 'textbox';} The `DropdownQuestion` control type presents a list of choices in a select box. import {QuestionBase} from './question-base';export class DropdownQuestion extends QuestionBase<string> { override controlType = 'dropdown';} ### [Compose form groups](https://angular.dev/#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. import {Injectable} from '@angular/core';import {FormControl, FormGroup, Validators} from '@angular/forms';import {QuestionBase} from './question-base';@Injectable()export class QuestionControlService { toFormGroup(questions: QuestionBase<string>[]) { const group: any = {}; questions.forEach((question) => { group[question.key] = question.required ? new FormControl(question.value || '', Validators.required) : new FormControl(question.value || ''); }); return new FormGroup(group); }} ## [Compose dynamic form contents](https://angular.dev/#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](https://angular.dev/api/forms/FormGroupDirective "API") 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. <div [formGroup]="form()"> <label [attr.for]="question().key">{{ question().label }}</label> <div> @switch (question().controlType) { @case ('textbox') { <input [formControlName]="question().key" [id]="question().key" [type]="question().type" /> } @case ('dropdown') { <select [id]="question().key" [formControlName]="question().key"> @for (opt of question().options; track opt) { <option [value]="opt.key">{{ opt.value }}</option> } </select> } } </div> @if (!isValid) { <div class="errorMessage">{{ question().label }} is required</div> }</div> import {Component, input, Input} from '@angular/core';import {FormGroup, ReactiveFormsModule} from '@angular/forms';import {QuestionBase} from './question-base';@Component({ selector: 'app-question', templateUrl: './dynamic-form-question.component.html', imports: [ReactiveFormsModule],})export class DynamicFormQuestionComponent { question = input.required<QuestionBase<string>>(); form = input.required<FormGroup>(); get isValid() { return this.form().controls[this.question().key].valid; }} The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model. You only have two types of questions at this point but you can imagine many more. The `ngSwitch` statement in the template determines which type of question to display. The switch uses directives with the [`formControlName`](https://angular.dev/api/forms/FormControlName "FormControlName") and [`formGroup`](https://angular.dev/api/forms/FormGroupDirective "FormGroupDirective") selectors. Both directives are defined in `ReactiveFormsModule`. ### [Supply data](https://angular.dev/#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. import {Injectable} from '@angular/core';import {DropdownQuestion} from './question-dropdown';import {QuestionBase} from './question-base';import {TextboxQuestion} from './question-textbox';import {of} from 'rxjs';@Injectable()export class QuestionService { // TODO: get from a remote source of question metadata getQuestions() { const questions: QuestionBase<string>[] = [ new DropdownQuestion({ key: 'favoriteAnimal', label: 'Favorite Animal', options: [ {key: 'cat', value: 'Cat'}, {key: 'dog', value: 'Dog'}, {key: 'horse', value: 'Horse'}, {key: 'capybara', value: 'Capybara'}, ], order: 3, }), new TextboxQuestion({ key: 'firstName', label: 'First name', value: 'Alex', required: true, order: 1, }), new TextboxQuestion({ key: 'emailAddress', label: 'Email', type: 'email', order: 2, }), ]; return of(questions.sort((a, b) => a.order - b.order)); }} ## [Create a dynamic form template](https://angular.dev/#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`. <div> <form (ngSubmit)="onSubmit()" [formGroup]="form"> @for (question of questions(); track question) { <div class="form-row"> <app-question [question]="question" [form]="form" /> </div> } <div class="form-row"> <button type="submit" [disabled]="!form.valid">Save</button> </div> </form> @if (payLoad) { <div class="form-row"><strong>Saved the following values</strong><br />{{ payLoad }}</div> }</div> import {Component, inject, input} from '@angular/core';import {FormGroup, ReactiveFormsModule} from '@angular/forms';import {DynamicFormQuestionComponent} from './dynamic-form-question.component';import {QuestionBase} from './question-base';import {QuestionControlService} from './question-control.service';@Component({ selector: 'app-dynamic-form', templateUrl: './dynamic-form.component.html', providers: [QuestionControlService], imports: [DynamicFormQuestionComponent, ReactiveFormsModule],})export class DynamicFormComponent { private readonly qcs = inject(QuestionControlService); questions = input<QuestionBase<string>[] | null>([]); form: FormGroup = this.qcs.toFormGroup(this.questions() as QuestionBase<string>[]); payLoad = ''; onSubmit() { this.payLoad = JSON.stringify(this.form.getRawValue()); }} ### [Display the form](https://angular.dev/#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>`. import {Component, inject} from '@angular/core';import {AsyncPipe} from '@angular/common';import {DynamicFormComponent} from './dynamic-form.component';import {QuestionService} from './question.service';import {QuestionBase} from './question-base';import {Observable} from 'rxjs';@Component({ selector: 'app-root', template: ` <div> <h2>Job Application for Heroes</h2> <app-dynamic-form [questions]="questions$ | async" /> </div> `, providers: [QuestionService], imports: [AsyncPipe, DynamicFormComponent],})export class AppComponent { questions$: Observable<QuestionBase<string>[]> = inject(QuestionService).getQuestions();} This separation of model and data lets you repurpose the components for any type of survey, as long as it's compatible with the _question_ object model. ### [Ensuring valid data](https://angular.dev/#ensuring-valid-data) The form template uses dynamic data binding of metadata to render the form without making any hardcoded assumptions about specific questions. It adds both control metadata and validation criteria dynamically. To ensure valid input, the _Save_ button is disabled until the form is in a valid state. When the form is valid, click _Save_ and the application renders the current form values as JSON. The following figure shows the final form.  ## [Next steps](https://angular.dev/#next-steps) [Validating form input](https://angular.dev/guide/forms/reactive-forms#validating-form-input) [Form validation guide](https://angular.dev/guide/forms/form-validation) --- ## Page: https://angular.dev/guide/forms/guide/forms/template-driven-forms ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/guide/forms ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/AbstractControl ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/FormControl ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/guide/forms/form-validation ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api#forms ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/guide/forms/reactive-forms ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/FormGroup ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/guide/templates/two-way-binding ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/guide/templates/variables#template-reference-variables ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/NgForm#properties ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/common/NgForOf ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/common/JsonPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/core/Directive#exportAs ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/Validators ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/AbstractControlOptions ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/FormGroupDirective ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/forms/api/forms/FormControlName ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/http Most front-end applications need to communicate with a server over the HTTP protocol, to download or upload data and access other back-end services. Angular provides a client HTTP API for Angular applications, the `HttpClient` service class in `@angular/common/http`. ## [HTTP client service features](https://angular.dev/#http-client-service-features) The HTTP client service offers the following major features: * The ability to request [typed response values](https://angular.dev/guide/http/making-requests#fetching-json-data) * Streamlined [error handling](https://angular.dev/guide/http/making-requests#handling-request-failure) * Request and response [interception](https://angular.dev/guide/http/interceptors) * Robust [testing utilities](https://angular.dev/guide/http/testing) ## [What's next](https://angular.dev/#whats-next) [Setting up HttpClient](https://angular.dev/guide/http/setup) [Making HTTP requests](https://angular.dev/guide/http/making-requests) --- ## Page: https://angular.dev/guide/http/setup Before you can use `HttpClient` in your app, you must configure it using [dependency injection](https://angular.dev/guide/di). ## [Providing `HttpClient` through dependency injection](https://angular.dev/#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`](https://angular.dev/#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`](https://angular.dev/#withfetch) export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withFetch(), ), ]}; By default, `HttpClient` uses the [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest) API to make requests. The `withFetch` feature switches the client to use the [`fetch`](https://developer.mozilla.org/docs/Web/API/Fetch_API) 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(...)`](https://angular.dev/#withinterceptors) `withInterceptors` configures the set of interceptor functions which will process requests made through `HttpClient`. See the [interceptor guide](https://angular.dev/guide/http/interceptors) for more information. ### [`withInterceptorsFromDi()`](https://angular.dev/#withinterceptorsfromdi) `withInterceptorsFromDi` includes the older style of class-based interceptors in the `HttpClient` configuration. See the [interceptor guide](https://angular.dev/guide/http/interceptors) for more information. **HELPFUL:** Functional interceptors (through `withInterceptors`) have more predictable ordering and we recommend them over DI-based interceptors. ### [`withRequestsMadeViaParent()`](https://angular.dev/#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()`](https://angular.dev/#withjsonpsupport) Including `withJsonpSupport` enables the `.jsonp()` method on `HttpClient`, which makes a GET request via the [JSONP convention](https://en.wikipedia.org/wiki/JSONP) for cross-domain loading of data. **HELPFUL:** Prefer using [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) to make cross-domain requests instead of JSONP when possible. ### [`withXsrfConfiguration(...)`](https://angular.dev/#withxsrfconfiguration) Including this option allows for customization of `HttpClient`'s built-in XSRF security functionality. See the [security guide](https://angular.dev/best-practices/security) for more information. ### [`withNoXsrfProtection()`](https://angular.dev/#withnoxsrfprotection) Including this option disables `HttpClient`'s built-in XSRF security functionality. See the [security guide](https://angular.dev/best-practices/security) for more information. ## [`HttpClientModule`\-based configuration](https://angular.dev/#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()` | ### Use caution when using HttpClientModule in multiple injectors 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. --- ## Page: https://angular.dev/guide/http/making-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`](https://rxjs.dev/guide/observable) which, when subscribed, sends the request and then emits the results when the server responds. **NOTE:** `Observable`s created by `HttpClient` 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](https://angular.dev/#fetching-json-data) Fetching data from a backend often requires making a GET request using the [`HttpClient.get()`](https://angular.dev/api/common/http/HttpClient#get) method. This method takes two arguments: the string endpoint URL from which to fetch, and an _optional options_ object to configure the request. For example, to fetch configuration data from a hypothetical API using the `HttpClient.get()` 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](https://angular.dev/#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`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) containing the raw response bytes | | `'blob'` | [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) instance | For example, you can ask `HttpClient` to download the raw bytes of a `.jpeg` image into an `ArrayBuffer`: http.get('/images/dog.jpg', {responseType: 'arraybuffer'}).subscribe(buffer => { console.log('The image is ' + buffer.byteLength + ' bytes large');}); ### Literal value for `responseType` Because the value of `responseType` affects the type returned by `HttpClient`, 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](https://angular.dev/#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()`](https://angular.dev/api/common/http/HttpClient#post) method behaves similarly to `get()`, and accepts an additional `body` argument before its options: http.post<Config>('/api/config', newConfig).subscribe(config => { console.log('Updated config:', config);}); Many different types of values can be provided as the request's `body`, and `HttpClient` will serialize them accordingly: | **`body` type** | **Serialized as** | | --- | --- | | string | Plain text | | number, boolean, array, or plain object | JSON | | [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | raw data from the buffer | | [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) | raw data with the `Blob`'s content type | | [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) | `multipart/form-data` encoded data | | [`HttpParams`](https://angular.dev/api/common/http/HttpParams) or [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) | `application/x-www-form-urlencoded` formatted string | **IMPORTANT:** Remember to `.subscribe()` to mutation request `Observable`s in order to actually fire the request. ## [Setting URL parameters](https://angular.dev/#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. 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](https://angular.dev/#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);}); ### Literal value for `observe` 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](https://angular.dev/#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; }}); ### Literal value for `observe` 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](https://angular.dev/#handling-request-failure) There are two ways an HTTP request can fail: * A network or connection error can prevent the request from reaching the backend server. * The backend can receive the request but fail to process it, and return an error response. `HttpClient` captures both kinds of errors in an `HttpErrorResponse` which it returns through the `Observable`'s error channel. Network errors have a `status` code of `0` and an `error` which is an instance of [`ProgressEvent`](https://developer.mozilla.org/docs/Web/API/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](https://rxjs.dev/) offers several operators which can be useful for error handling. You can use the `catchError` operator to transform an error response into a value for the UI. This value can tell the UI to display an error page or value, and capture the error's cause if necessary. Sometimes transient errors such as network interruptions can cause a request to fail unexpectedly, and simply retrying the request will allow it to succeed. RxJS provides several _retry_ operators which automatically re-subscribe to a failed `Observable` under certain conditions. For example, the `retry()` operator will automatically attempt to re-subscribe a specified number of times. ## [Http `Observable`s](https://angular.dev/#http-observables) Each request method on `HttpClient` constructs and returns an `Observable` of the requested response type. Understanding how these `Observable`s work is important when using `HttpClient`. `HttpClient` produces what RxJS calls "cold" `Observable`s, meaning that no actual request happens until the `Observable` is subscribed. Only then is the request actually dispatched to the server. Subscribing to the same `Observable` multiple times will trigger multiple backend requests. Each subscription is independent. **TIP:** You can think of `HttpClient` `Observable`s as _blueprints_ for actual server requests. Once subscribed, unsubscribing will abort the in-progress request. This is very useful if the `Observable` is subscribed via the `async` pipe, as it will automatically cancel the request if the user navigates away from the current page. Additionally, if you use the `Observable` with an RxJS combinator like `switchMap`, this cancellation will clean up any stale requests. Once the response returns, `Observable`s from `HttpClient` 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 `Observable`s ensures that subscriptions are disposed properly. ## [Best practices](https://angular.dev/#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 { @Input() userId!: string; user$!: Observable<User>; private userService = inject(UserService); constructor(): void { this.user$ = this.userService.getUser(this.userId); }} --- ## Page: https://angular.dev/guide/http/interceptors `HttpClient` supports a form of middleware known as _interceptors_. **TL;DR:** Interceptors are middleware that allows common patterns around retrying, caching, logging, and authentication to be abstracted away from individual requests. `HttpClient` 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](https://angular.dev/#di-based-interceptors) in their own section at the end. ## [Interceptors](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#intercepting-response-events) An interceptor may transform the `Observable` stream of `HttpEvent`s returned by `next` in order to access or manipulate the response. Because this stream includes all response events, inspecting the `.type` of each event may be necessary in order to identify the final response object. export function loggingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { return next(req).pipe(tap(event => { if (event.type === HttpEventType.Response) { console.log(req.url, 'returned a response with status', event.status); } }));} **TIP:** Interceptors naturally associate responses with their outgoing requests, because they transform the response stream in a closure that captures the request object. ## [Modifying requests](https://angular.dev/#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](https://angular.dev/#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);} Often it's useful to include information in a request that's not sent to the backend, but is specifically meant for interceptors. `HttpRequest`s 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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#setting-context-tokens-when-making-a-request) When making a request via the `HttpClient` API, you can provide values for `HttpContextToken`s: 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](https://angular.dev/#the-request-context-is-mutable) Unlike other properties of `HttpRequest`s, 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](https://angular.dev/#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',}); ## [DI-based interceptors](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/http/testing As for any external dependency, you must mock the HTTP backend so your tests can simulate interaction with a remote server. The `@angular/common/http/testing` library provides tools to capture requests made by the application, make assertions about them, and mock the responses to emulate your backend's behavior. The testing library is designed for a pattern 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](https://angular.dev/#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 `provideHttpCient()`. 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](https://angular.dev/#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$ = this.configService.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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#testing-error-handling) You should test your app's responses when HTTP requests fail. ### [Backend errors](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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 }, ],}); --- ## Page: https://angular.dev/guide/http/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/http/guide/http/interceptors ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/http/best-practices/security ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/http/api/common/http/HttpClient#get ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/http/api/common/http/HttpParams ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/performance One of the top priorities of any developer is ensuring that their application is as performant as possible. These guides are here to help you follow best practices for building performant applications by taking advantage of different rendering strategies. | Guides Types | Description | | --- | --- | | [Server-side rendering](https://angular.dev/guide/ssr) | Learn how to leverage rendering pages on the server to improve load times. | | [Build-time prerendering](https://angular.dev/guide/prerendering) | Also known as static-site generation (SSG), is an alternate rendering method to improve load times. | | [Hydration](https://angular.dev/guide/hydration) | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible. | --- ## Page: https://angular.dev/guide/testing Testing your Angular application helps you check that your application is working as you expect. ## [Set up testing](https://angular.dev/#set-up-testing) The Angular CLI downloads and installs everything you need to test an Angular application with [Jasmine testing framework](https://jasmine.github.io/). The project you create with the CLI is immediately ready to test. Just run the [`ng test`](https://angular.dev/cli/test) CLI command: ng test The `ng test` command builds the application in _watch mode_, and launches the [Karma test runner](https://karma-runner.github.io/). The console output looks like below: 02 11 2022 09:08:28.605:INFO [karma-server]: Karma v6.4.1 server started at http://localhost:9876/02 11 2022 09:08:28.607:INFO [launcher]: Launching browsers Chrome with concurrency unlimited02 11 2022 09:08:28.620:INFO [launcher]: Starting browser Chrome02 11 2022 09:08:31.312:INFO [Chrome]: Connected on socket -LaEYvD2R7MdcS0-AAAB with id 31534482Chrome: 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](https://github.com/dfederm/karma-jasmine-html-reporter).  Click on a test row to re-run just that test or click on a description to re-run the tests in the selected test group ("test suite"). Meanwhile, the `ng test` command is watching for changes. To see this in action, make a small change to `app.component.ts` and save. The tests run again, the browser refreshes, and the new test results appear. ## [Configuration](https://angular.dev/#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 ### [Other test frameworks](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#more-information-on-testing) After you've set up your application for testing, you might find the following testing guides useful. | | Details | | --- | --- | | [Code coverage](https://angular.dev/guide/testing/code-coverage) | How much of your app your tests are covering and how to specify required amounts. | | [Testing services](https://angular.dev/guide/testing/services) | How to test the services your application uses. | | [Basics of testing components](https://angular.dev/guide/testing/components-basics) | Basics of testing Angular components. | | [Component testing scenarios](https://angular.dev/guide/testing/components-scenarios) | Various kinds of component testing scenarios and use cases. | | [Testing attribute directives](https://angular.dev/guide/testing/attribute-directives) | How to test your attribute directives. | | [Testing pipes](https://angular.dev/guide/testing/pipes) | How to test pipes. | | [Debugging tests](https://angular.dev/guide/testing/debugging) | Common testing bugs. | | [Testing utility APIs](https://angular.dev/guide/testing/utility-apis) | Angular testing features. | --- ## Page: https://angular.dev/guide/testing/code-coverage 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](https://angular.dev/#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](https://karma-runner.github.io/) 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](https://angular.dev/guide/testing#configuration). 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](https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md). --- ## Page: https://angular.dev/guide/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. import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService { override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => { // Straight Jasmine testing without Angular's testing support describe('ValueService', () => { let service: ValueService; beforeEach(() => { service = new ValueService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe((value) => { expect(value).toBe('observable value'); done(); }); }); it('#getPromiseValue should return value from a promise', (done: DoneFn) => { service.getPromiseValue().then((value) => { expect(value).toBe('promise value'); done(); }); }); }); // MasterService requires injection of a ValueService describe('MasterService without Angular testing support', () => { let masterService: MasterService; it('#getValue should return real value from the real service', () => { masterService = new MasterService(new ValueService()); expect(masterService.getValue()).toBe('real value'); }); it('#getValue should return faked value from a fakeService', () => { masterService = new MasterService(new FakeValueService()); expect(masterService.getValue()).toBe('faked service value'); }); it('#getValue should return faked value from a fake object', () => { const fake = {getValue: () => 'fake value'}; masterService = new MasterService(fake as ValueService); expect(masterService.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a spy', () => { // create `getValue` spy on an object representing the ValueService const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); // set the value to return when the `getValue` spy is called. const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('MasterService (no beforeEach)', () => { it('#getValue should return stubbed value from a spy', () => { const {masterService, stubValue, valueServiceSpy} = setup(); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return {masterService, stubValue, valueServiceSpy}; } }); describe('ReversePipe', () => { let pipe: ReversePipe; beforeEach(() => { pipe = new ReversePipe(); }); it('transforms "abc" to "cba"', () => { expect(pipe.transform('abc')).toBe('cba'); }); it('no change to palindrome: "able was I ere I saw elba"', () => { const palindrome = 'able was I ere I saw elba'; expect(pipe.transform(palindrome)).toBe(palindrome); }); }); describe('LightswitchComp', () => { it('#clicked() should toggle #isOn', () => { const comp = new LightswitchComponent(); expect(comp.isOn).withContext('off at first').toBe(false); comp.clicked(); expect(comp.isOn).withContext('on after click').toBe(true); comp.clicked(); expect(comp.isOn).withContext('off after second click').toBe(false); }); it('#clicked() should set #message to "is on"', () => { const comp = new LightswitchComponent(); expect(comp.message) .withContext('off at first') .toMatch(/is off/i); comp.clicked(); expect(comp.message).withContext('on after clicked').toMatch(/is on/i); }); });}); ## [Services with dependencies](https://angular.dev/#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: import { Component, ContentChildren, Directive, EventEmitter, HostBinding, HostListener, inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Optional, Output, Pipe, PipeTransform, SimpleChanges,} from '@angular/core';import {FormsModule} from '@angular/forms';import {of} from 'rxjs';import {delay} from 'rxjs/operators';import {sharedImports} from '../shared/shared';////////// The App: Services and Components for the tests. //////////////export interface Hero { name: string;}////////// Services ///////////////@Injectable()export class ValueService { value = 'real value'; getValue() { return this.value; } setValue(value: string) { this.value = value; } getObservableValue() { return of('observable value'); } getPromiseValue() { return Promise.resolve('promise value'); } getObservableDelayValue() { return of('observable delay value').pipe(delay(10)); }}@Injectable()export class MasterService { public valueService = inject(ValueService); getValue() { return this.valueService.getValue(); }}/////////// Pipe /////////////////* * Reverse the input string. */@Pipe({name: 'reverse'})export class ReversePipe implements PipeTransform { transform(s: string) { let r = ''; for (let i = s.length; i; ) { r += s[--i]; } return r; }}//////////// Components /////////////@Component({ selector: 'bank-account', template: ` Bank Name: {{ bank }} Account Id: {{ id }} `,})export class BankAccountComponent { @Input() bank = ''; @Input('account') id = '';}/** A component with attributes, styles, classes, and property setting */@Component({ selector: 'bank-account-parent', template: ` <bank-account bank="RBC" account="4747" [style.width.px]="width" [style.color]="color" [class.closed]="isClosed" [class.open]="!isClosed" /> `, imports: [BankAccountComponent],})export class BankAccountParentComponent { width = 200; color = 'red'; isClosed = true;}@Component({ selector: 'lightswitch-comp', template: ` <button type="button" (click)="clicked()">Click me!</button> <span>{{ message }}</span>`,})export class LightswitchComponent { isOn = false; clicked() { this.isOn = !this.isOn; } get message() { return `The light is ${this.isOn ? 'On' : 'Off'}`; }}@Component({ selector: 'child-1', template: '<span>Child-1({{text}})</span>',})export class Child1Component { @Input() text = 'Original';}@Component({ selector: 'child-2', template: '<div>Child-2({{text}})</div>',})export class Child2Component { @Input() text = '';}@Component({ selector: 'child-3', template: '<div>Child-3({{text}})</div>',})export class Child3Component { @Input() text = '';}@Component({ selector: 'input-comp', template: '<input [(ngModel)]="name">', imports: [FormsModule],})export class InputComponent { name = 'John';}/* Prefer this metadata syntax */// @Directive({// selector: 'input[value]',// host: {// '[value]': 'value',// '(input)': 'valueChange.emit($event.target.value)'// },// inputs: ['value'],// outputs: ['valueChange']// })// export class InputValueBinderDirective {// value: any;// valueChange: EventEmitter<any> = new EventEmitter();// }// As the styleguide recommends@Directive({selector: 'input[value]'})export class InputValueBinderDirective { @HostBinding() @Input() value: any; @Output() valueChange: EventEmitter<any> = new EventEmitter(); @HostListener('input', ['$event.target.value']) onInput(value: any) { this.valueChange.emit(value); }}@Component({ selector: 'input-value-comp', template: ` Name: <input [value]="name" /> {{ name }} `,})export class InputValueBinderComponent { name = 'Sally'; // initial value}@Component({ selector: 'parent-comp', imports: [Child1Component], template: 'Parent(<child-1></child-1>)',})export class ParentComponent {}@Component({ selector: 'io-comp', template: '<button type="button" class="hero" (click)="click()">Original {{hero.name}}</button>',})export class IoComponent { @Input() hero!: Hero; @Output() selected = new EventEmitter<Hero>(); click() { this.selected.emit(this.hero); }}@Component({ selector: 'io-parent-comp', template: ` @if (!selectedHero) { <p><i>Click to select a hero</i></p> } @if (selectedHero) { <p>The selected hero is {{ selectedHero.name }}</p> } @for (hero of heroes; track hero) { <io-comp [hero]="hero" (selected)="onSelect($event)"> </io-comp> } `, imports: [IoComponent, sharedImports],})export class IoParentComponent { heroes: Hero[] = [{name: 'Bob'}, {name: 'Carol'}, {name: 'Ted'}, {name: 'Alice'}]; selectedHero!: Hero; onSelect(hero: Hero) { this.selectedHero = hero; }}@Component({ selector: 'my-if-comp', template: 'MyIf(@if (showMore) {<span>More</span>})', imports: [sharedImports],})export class MyIfComponent { showMore = false;}@Component({ selector: 'my-service-comp', template: 'injected value: {{valueService.value}}', providers: [ValueService],})export class TestProvidersComponent { public valueService = inject(ValueService);}@Component({ selector: 'my-service-comp', template: 'injected value: {{valueService.value}}', viewProviders: [ValueService],})export class TestViewProvidersComponent { public valueService = inject(ValueService);}@Component({ selector: 'external-template-comp', templateUrl: './demo-external-template.html',})export class ExternalTemplateComponent { private service = inject(ValueService, {optional: true}); serviceValue = this.service?.getValue() ?? '';}@Component({ selector: 'comp-w-ext-comp', imports: [ExternalTemplateComponent], template: ` <h3>comp-w-ext-comp</h3> <external-template-comp></external-template-comp> `,})export class InnerCompWithExternalTemplateComponent {}@Component({selector: 'needs-content', template: '<ng-content></ng-content>'})export class NeedsContentComponent { // children with #content local variable @ContentChildren('content') children: any;}///////// MyIfChildComp ////////@Component({ selector: 'my-if-child-1', template: ` <h4>MyIfChildComp</h4> <div> <label for="child-value" >Child value: <input id="child-value" [(ngModel)]="childValue" /> </label> </div> <p><i>Change log:</i></p> @for (log of changeLog; track log; let i = $index) { <div>{{ i + 1 }} - {{ log }}</div> }`, imports: [FormsModule, sharedImports],})export class MyIfChildComponent implements OnInit, OnChanges, OnDestroy { @Input() value = ''; @Output() valueChange = new EventEmitter<string>(); get childValue() { return this.value; } set childValue(v: string) { if (this.value === v) { return; } this.value = v; this.valueChange.emit(v); } changeLog: string[] = []; ngOnInitCalled = false; ngOnChangesCounter = 0; ngOnDestroyCalled = false; ngOnInit() { this.ngOnInitCalled = true; this.changeLog.push('ngOnInit called'); } ngOnDestroy() { this.ngOnDestroyCalled = true; this.changeLog.push('ngOnDestroy called'); } ngOnChanges(changes: SimpleChanges) { for (const propName in changes) { this.ngOnChangesCounter += 1; const prop = changes[propName]; const cur = JSON.stringify(prop.currentValue); const prev = JSON.stringify(prop.previousValue); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); } }}///////// MyIfParentComp ////////@Component({ selector: 'my-if-parent-comp', template: ` <h3>MyIfParentComp</h3> <label for="parent" >Parent value: <input id="parent" [(ngModel)]="parentValue" /> </label> <button type="button" (click)="clicked()">{{ toggleLabel }} Child</button><br /> @if (showChild) { <div style="margin: 4px; padding: 4px; background-color: aliceblue;"> <my-if-child-1 [(value)]="parentValue"></my-if-child-1> </div> } `, imports: [FormsModule, MyIfChildComponent, sharedImports],})export class MyIfParentComponent implements OnInit { ngOnInitCalled = false; parentValue = 'Hello, World'; showChild = false; toggleLabel = 'Unknown'; ngOnInit() { this.ngOnInitCalled = true; this.clicked(); } clicked() { this.showChild = !this.showChild; this.toggleLabel = this.showChild ? 'Close' : 'Show'; }}@Component({ selector: 'reverse-pipe-comp', template: ` <input [(ngModel)]="text" /> <span>{{ text | reverse }}</span> `, imports: [ReversePipe, FormsModule],})export class ReversePipeComponent { text = 'my dog has fleas.';}@Component({ imports: [NeedsContentComponent], template: '<div>Replace Me</div>',})export class ShellComponent {}@Component({ selector: 'demo-comp', template: ` <h1>Specs Demo</h1> <my-if-parent-comp></my-if-parent-comp> <hr> <h3>Input/Output Component</h3> <io-parent-comp></io-parent-comp> <hr> <h3>External Template Component</h3> <external-template-comp></external-template-comp> <hr> <h3>Component With External Template Component</h3> <comp-w-ext-comp></comp-w-ext-comp> <hr> <h3>Reverse Pipe</h3> <reverse-pipe-comp></reverse-pipe-comp> <hr> <h3>InputValueBinder Directive</h3> <input-value-comp></input-value-comp> <hr> <h3>Button Component</h3> <lightswitch-comp></lightswitch-comp> <hr> <h3>Needs Content</h3> <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, imports: [ Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InnerCompWithExternalTemplateComponent, InputValueBinderComponent, IoParentComponent, LightswitchComponent, NeedsContentComponent, ReversePipeComponent, MyIfParentComponent, ],})export class DemoComponent {}//////// Aggregations ////////////export const demoProviders = [MasterService, ValueService]; `MasterService` delegates its only method, `getValue`, to the injected `ValueService`. Here are several ways to test it. import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService { override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => { // Straight Jasmine testing without Angular's testing support describe('ValueService', () => { let service: ValueService; beforeEach(() => { service = new ValueService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe((value) => { expect(value).toBe('observable value'); done(); }); }); it('#getPromiseValue should return value from a promise', (done: DoneFn) => { service.getPromiseValue().then((value) => { expect(value).toBe('promise value'); done(); }); }); }); // MasterService requires injection of a ValueService describe('MasterService without Angular testing support', () => { let masterService: MasterService; it('#getValue should return real value from the real service', () => { masterService = new MasterService(new ValueService()); expect(masterService.getValue()).toBe('real value'); }); it('#getValue should return faked value from a fakeService', () => { masterService = new MasterService(new FakeValueService()); expect(masterService.getValue()).toBe('faked service value'); }); it('#getValue should return faked value from a fake object', () => { const fake = {getValue: () => 'fake value'}; masterService = new MasterService(fake as ValueService); expect(masterService.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a spy', () => { // create `getValue` spy on an object representing the ValueService const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); // set the value to return when the `getValue` spy is called. const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('MasterService (no beforeEach)', () => { it('#getValue should return stubbed value from a spy', () => { const {masterService, stubValue, valueServiceSpy} = setup(); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return {masterService, stubValue, valueServiceSpy}; } }); describe('ReversePipe', () => { let pipe: ReversePipe; beforeEach(() => { pipe = new ReversePipe(); }); it('transforms "abc" to "cba"', () => { expect(pipe.transform('abc')).toBe('cba'); }); it('no change to palindrome: "able was I ere I saw elba"', () => { const palindrome = 'able was I ere I saw elba'; expect(pipe.transform(palindrome)).toBe(palindrome); }); }); describe('LightswitchComp', () => { it('#clicked() should toggle #isOn', () => { const comp = new LightswitchComponent(); expect(comp.isOn).withContext('off at first').toBe(false); comp.clicked(); expect(comp.isOn).withContext('on after click').toBe(true); comp.clicked(); expect(comp.isOn).withContext('off after second click').toBe(false); }); it('#clicked() should set #message to "is on"', () => { const comp = new LightswitchComponent(); expect(comp.message) .withContext('off at first') .toMatch(/is off/i); comp.clicked(); expect(comp.message).withContext('on after clicked').toMatch(/is on/i); }); });}); 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`](https://angular.dev/#testing-services-with-the-testbed) Your application relies on Angular [dependency injection (DI)](https://angular.dev/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`](https://angular.dev/#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](https://angular.dev/guide/ngmodules). The `TestBed.configureTestingModule()` method takes a metadata object that can have most of the properties of an [@NgModule](https://angular.dev/guide/ngmodules). To test a service, you set the `providers` metadata property with an array of the services that you'll test or mock. import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} 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. import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} Or inside the `beforeEach()` if you prefer to inject the service as part of your setup. import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} When testing a service with a dependency, provide the mock in the `providers` array. In the following example, the mock is a spy object. import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} The test consumes that spy in the same way it did earlier. import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} ## [Testing without `beforeEach()`](https://angular.dev/#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()`. import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService { override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => { // Straight Jasmine testing without Angular's testing support describe('ValueService', () => { let service: ValueService; beforeEach(() => { service = new ValueService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe((value) => { expect(value).toBe('observable value'); done(); }); }); it('#getPromiseValue should return value from a promise', (done: DoneFn) => { service.getPromiseValue().then((value) => { expect(value).toBe('promise value'); done(); }); }); }); // MasterService requires injection of a ValueService describe('MasterService without Angular testing support', () => { let masterService: MasterService; it('#getValue should return real value from the real service', () => { masterService = new MasterService(new ValueService()); expect(masterService.getValue()).toBe('real value'); }); it('#getValue should return faked value from a fakeService', () => { masterService = new MasterService(new FakeValueService()); expect(masterService.getValue()).toBe('faked service value'); }); it('#getValue should return faked value from a fake object', () => { const fake = {getValue: () => 'fake value'}; masterService = new MasterService(fake as ValueService); expect(masterService.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a spy', () => { // create `getValue` spy on an object representing the ValueService const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); // set the value to return when the `getValue` spy is called. const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('MasterService (no beforeEach)', () => { it('#getValue should return stubbed value from a spy', () => { const {masterService, stubValue, valueServiceSpy} = setup(); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return {masterService, stubValue, valueServiceSpy}; } }); describe('ReversePipe', () => { let pipe: ReversePipe; beforeEach(() => { pipe = new ReversePipe(); }); it('transforms "abc" to "cba"', () => { expect(pipe.transform('abc')).toBe('cba'); }); it('no change to palindrome: "able was I ere I saw elba"', () => { const palindrome = 'able was I ere I saw elba'; expect(pipe.transform(palindrome)).toBe(palindrome); }); }); describe('LightswitchComp', () => { it('#clicked() should toggle #isOn', () => { const comp = new LightswitchComponent(); expect(comp.isOn).withContext('off at first').toBe(false); comp.clicked(); expect(comp.isOn).withContext('on after click').toBe(true); comp.clicked(); expect(comp.isOn).withContext('off after second click').toBe(false); }); it('#clicked() should set #message to "is on"', () => { const comp = new LightswitchComponent(); expect(comp.message) .withContext('off at first') .toMatch(/is off/i); comp.clicked(); expect(comp.message).withContext('on after clicked').toMatch(/is on/i); }); });}); 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. import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService { override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => { // Straight Jasmine testing without Angular's testing support describe('ValueService', () => { let service: ValueService; beforeEach(() => { service = new ValueService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe((value) => { expect(value).toBe('observable value'); done(); }); }); it('#getPromiseValue should return value from a promise', (done: DoneFn) => { service.getPromiseValue().then((value) => { expect(value).toBe('promise value'); done(); }); }); }); // MasterService requires injection of a ValueService describe('MasterService without Angular testing support', () => { let masterService: MasterService; it('#getValue should return real value from the real service', () => { masterService = new MasterService(new ValueService()); expect(masterService.getValue()).toBe('real value'); }); it('#getValue should return faked value from a fakeService', () => { masterService = new MasterService(new FakeValueService()); expect(masterService.getValue()).toBe('faked service value'); }); it('#getValue should return faked value from a fake object', () => { const fake = {getValue: () => 'fake value'}; masterService = new MasterService(fake as ValueService); expect(masterService.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a spy', () => { // create `getValue` spy on an object representing the ValueService const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); // set the value to return when the `getValue` spy is called. const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('MasterService (no beforeEach)', () => { it('#getValue should return stubbed value from a spy', () => { const {masterService, stubValue, valueServiceSpy} = setup(); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return {masterService, stubValue, valueServiceSpy}; } }); describe('ReversePipe', () => { let pipe: ReversePipe; beforeEach(() => { pipe = new ReversePipe(); }); it('transforms "abc" to "cba"', () => { expect(pipe.transform('abc')).toBe('cba'); }); it('no change to palindrome: "able was I ere I saw elba"', () => { const palindrome = 'able was I ere I saw elba'; expect(pipe.transform(palindrome)).toBe(palindrome); }); }); describe('LightswitchComp', () => { it('#clicked() should toggle #isOn', () => { const comp = new LightswitchComponent(); expect(comp.isOn).withContext('off at first').toBe(false); comp.clicked(); expect(comp.isOn).withContext('on after click').toBe(true); comp.clicked(); expect(comp.isOn).withContext('off after second click').toBe(false); }); it('#clicked() should set #message to "is on"', () => { const comp = new LightswitchComponent(); expect(comp.message) .withContext('off at first') .toMatch(/is off/i); comp.clicked(); expect(comp.message).withContext('on after clicked').toMatch(/is on/i); }); });}); Notice how the test uses [_destructuring assignment_](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to extract the setup variables that it needs. import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService { override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => { // Straight Jasmine testing without Angular's testing support describe('ValueService', () => { let service: ValueService; beforeEach(() => { service = new ValueService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe((value) => { expect(value).toBe('observable value'); done(); }); }); it('#getPromiseValue should return value from a promise', (done: DoneFn) => { service.getPromiseValue().then((value) => { expect(value).toBe('promise value'); done(); }); }); }); // MasterService requires injection of a ValueService describe('MasterService without Angular testing support', () => { let masterService: MasterService; it('#getValue should return real value from the real service', () => { masterService = new MasterService(new ValueService()); expect(masterService.getValue()).toBe('real value'); }); it('#getValue should return faked value from a fakeService', () => { masterService = new MasterService(new FakeValueService()); expect(masterService.getValue()).toBe('faked service value'); }); it('#getValue should return faked value from a fake object', () => { const fake = {getValue: () => 'fake value'}; masterService = new MasterService(fake as ValueService); expect(masterService.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a spy', () => { // create `getValue` spy on an object representing the ValueService const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); // set the value to return when the `getValue` spy is called. const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); masterService = new MasterService(valueServiceSpy); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('MasterService (no beforeEach)', () => { it('#getValue should return stubbed value from a spy', () => { const {masterService, stubValue, valueServiceSpy} = setup(); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); function setup() { const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']); const stubValue = 'stub value'; const masterService = new MasterService(valueServiceSpy); valueServiceSpy.getValue.and.returnValue(stubValue); return {masterService, stubValue, valueServiceSpy}; } }); describe('ReversePipe', () => { let pipe: ReversePipe; beforeEach(() => { pipe = new ReversePipe(); }); it('transforms "abc" to "cba"', () => { expect(pipe.transform('abc')).toBe('cba'); }); it('no change to palindrome: "able was I ere I saw elba"', () => { const palindrome = 'able was I ere I saw elba'; expect(pipe.transform(palindrome)).toBe(palindrome); }); }); describe('LightswitchComp', () => { it('#clicked() should toggle #isOn', () => { const comp = new LightswitchComponent(); expect(comp.isOn).withContext('off at first').toBe(false); comp.clicked(); expect(comp.isOn).withContext('on after click').toBe(true); comp.clicked(); expect(comp.isOn).withContext('off after second click').toBe(false); }); it('#clicked() should set #message to "is on"', () => { const comp = new LightswitchComponent(); expect(comp.message) .withContext('off at first') .toMatch(/is off/i); comp.clicked(); expect(comp.message).withContext('on after clicked').toMatch(/is on/i); }); });}); 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](https://github.com/angular/angular-cli) generate test files with `beforeEach()` and `TestBed`, feel free to adopt _this alternative approach_ in your own projects. ## [Testing HTTP services](https://angular.dev/#testing-http-services) Data services that make HTTP calls to remote servers typically inject and delegate to the Angular [`HttpClient`](https://angular.dev/guide/http/testing) service for XHR calls. You can test a data service with an injected `HttpClient` spy as you would test any service with a dependency. import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';// Other importsimport {TestBed} from '@angular/core/testing';import {HttpClient, HttpResponse, HttpErrorResponse} from '@angular/common/http';import {asyncData, asyncError} from '../../testing/async-observable-helpers';import {Hero} from './hero';import {HeroService} from './hero.service';describe('HeroesService (with spies)', () => { let httpClientSpy: jasmine.SpyObj<HttpClient>; let heroService: HeroService; beforeEach(() => { // TODO: spy on other methods too httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); heroService = new HeroService(httpClientSpy); }); it('should return expected heroes (HttpClient called once)', (done: DoneFn) => { const expectedHeroes: Hero[] = [ {id: 1, name: 'A'}, {id: 2, name: 'B'}, ]; httpClientSpy.get.and.returnValue(asyncData(expectedHeroes)); heroService.getHeroes().subscribe({ next: (heroes) => { expect(heroes).withContext('expected heroes').toEqual(expectedHeroes); done(); }, error: done.fail, }); expect(httpClientSpy.get.calls.count()).withContext('one call').toBe(1); }); it('should return an error when the server returns a 404', (done: DoneFn) => { const errorResponse = new HttpErrorResponse({ error: 'test 404 error', status: 404, statusText: 'Not Found', }); httpClientSpy.get.and.returnValue(asyncError(errorResponse)); heroService.getHeroes().subscribe({ next: (heroes) => done.fail('expected an error, not heroes'), error: (error) => { expect(error.message).toContain('test 404 error'); done(); }, }); });});describe('HeroesService (with mocks)', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; let heroService: HeroService; beforeEach(() => { TestBed.configureTestingModule({ // Import the HttpClient mocking services imports: [HttpClientTestingModule], // Provide the service-under-test providers: [HeroService], }); // Inject the http, test controller, and service-under-test // as they will be referenced by each test. httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); heroService = TestBed.inject(HeroService); }); afterEach(() => { // After every test, assert that there are no more pending requests. httpTestingController.verify(); }); /// HeroService method tests begin /// describe('#getHeroes', () => { let expectedHeroes: Hero[]; beforeEach(() => { heroService = TestBed.inject(HeroService); expectedHeroes = [ {id: 1, name: 'A'}, {id: 2, name: 'B'}, ] as Hero[]; }); it('should return expected heroes (called once)', () => { heroService.getHeroes().subscribe({ next: (heroes) => expect(heroes).withContext('should return expected heroes').toEqual(expectedHeroes), error: fail, }); // HeroService should have made one request to GET heroes from expected URL const req = httpTestingController.expectOne(heroService.heroesUrl); expect(req.request.method).toEqual('GET'); // Respond with the mock heroes req.flush(expectedHeroes); }); it('should be OK returning no heroes', () => { heroService.getHeroes().subscribe({ next: (heroes) => expect(heroes.length).withContext('should have empty heroes array').toEqual(0), error: fail, }); const req = httpTestingController.expectOne(heroService.heroesUrl); req.flush([]); // Respond with no heroes }); it('should turn 404 into a user-friendly error', () => { const msg = 'Deliberate 404'; heroService.getHeroes().subscribe({ next: (heroes) => fail('expected to fail'), error: (error) => expect(error.message).toContain(msg), }); const req = httpTestingController.expectOne(heroService.heroesUrl); // respond with a 404 and the error message in the body req.flush(msg, {status: 404, statusText: 'Not Found'}); }); it('should return expected heroes (called multiple times)', () => { heroService.getHeroes().subscribe(); heroService.getHeroes().subscribe(); heroService.getHeroes().subscribe({ next: (heroes) => expect(heroes).withContext('should return expected heroes').toEqual(expectedHeroes), error: fail, }); const requests = httpTestingController.match(heroService.heroesUrl); expect(requests.length).withContext('calls to getHeroes()').toEqual(3); // Respond to each request with different mock hero results requests[0].flush([]); requests[1].flush([{id: 1, name: 'bob'}]); requests[2].flush(expectedHeroes); }); }); describe('#updateHero', () => { // Expecting the query form of URL so should not 404 when id not found const makeUrl = (id: number) => `${heroService.heroesUrl}/?id=${id}`; it('should update a hero and return it', () => { const updateHero: Hero = {id: 1, name: 'A'}; heroService.updateHero(updateHero).subscribe({ next: (data) => expect(data).withContext('should return the hero').toEqual(updateHero), error: fail, }); // HeroService should have made one request to PUT hero const req = httpTestingController.expectOne(heroService.heroesUrl); expect(req.request.method).toEqual('PUT'); expect(req.request.body).toEqual(updateHero); // Expect server to return the hero after PUT const expectedResponse = new HttpResponse({ status: 200, statusText: 'OK', body: updateHero, }); req.event(expectedResponse); }); it('should turn 404 error into user-facing error', () => { const msg = 'Deliberate 404'; const updateHero: Hero = {id: 1, name: 'A'}; heroService.updateHero(updateHero).subscribe({ next: (heroes) => fail('expected to fail'), error: (error) => expect(error.message).toContain(msg), }); const req = httpTestingController.expectOne(heroService.heroesUrl); // respond with a 404 and the error message in the body req.flush(msg, {status: 404, statusText: 'Not Found'}); }); it('should turn network error into user-facing error', (done) => { // Create mock ProgressEvent with type `error`, raised when something goes wrong at // the network level. Connection timeout, DNS error, offline, etc. const errorEvent = new ProgressEvent('error'); const updateHero: Hero = {id: 1, name: 'A'}; heroService.updateHero(updateHero).subscribe({ next: (heroes) => fail('expected to fail'), error: (error) => { expect(error).toBe(errorEvent); done(); }, }); const req = httpTestingController.expectOne(heroService.heroesUrl); // Respond with mock error req.error(errorEvent); }); }); // TODO: test other HeroService methods}); **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`](https://angular.dev/#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](https://angular.dev/guide/http/testing), which covers testing with the `HttpClientTestingModule` in detail. --- ## Page: https://angular.dev/guide/testing/components-basics A component, unlike all other parts of an Angular application, combines an HTML template and a TypeScript class. The component truly is the template and the class _working together_. To adequately test a component, you should test that they work together as intended. Such tests require creating the component's host element in the browser DOM, as Angular does, and investigating the component class's interaction with the DOM as described by its template. The Angular `TestBed` 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](https://angular.dev/#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](https://angular.dev/#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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); **HELPFUL:** Because `compileComponents` is asynchronous, it uses the [`waitForAsync`](https://angular.dev/api/core/testing/waitForAsync) utility function imported from `@angular/core/testing`. Refer to the [waitForAsync](https://angular.dev/guide/testing/components-scenarios#waitForAsync) section for more details. ### [Reduce the setup](https://angular.dev/#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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); In this example, the metadata object passed to `TestBed.configureTestingModule` simply declares `BannerComponent`, the component to test. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); **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()`](https://angular.dev/#createcomponent) After configuring `TestBed`, you call its `createComponent()` method. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); `TestBed.createComponent()` creates an instance of the `BannerComponent`, adds a corresponding element to the test-runner DOM, and returns a [`ComponentFixture`](https://angular.dev/#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`](https://angular.dev/#componentfixture) The [ComponentFixture](https://angular.dev/api/core/testing/ComponentFixture) is a test harness for interacting with the created component and its corresponding element. Access the component instance through the fixture and confirm it exists with a Jasmine expectation: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); ### [`beforeEach()`](https://angular.dev/#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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); Now add a test that gets the component's element from `fixture.nativeElement` and looks for the expected text. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); ### [`nativeElement`](https://angular.dev/#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](https://developer.mozilla.org/docs/Web/API/Web_Workers_API), 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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); ### [`DebugElement`](https://angular.dev/#debugelement) The Angular _fixture_ provides the component's element directly through the `fixture.nativeElement`. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); This is actually a convenience method, implemented as `fixture.debugElement.nativeElement`. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); 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`: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); 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. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); ### [`By.css()`](https://angular.dev/#bycss) Although the tests in this guide all run in the browser, some applications might run on a different platform at least some of the time. For example, the component might render first on the server as part of a strategy to make the application launch faster on poorly connected devices. The server-side renderer might not support the full HTML element API. If it doesn't support `querySelector`, the previous test could fail. The `DebugElement` 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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); The following example re-implements the previous test with `DebugElement.query()` and the browser's `By.css` method. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {BannerComponent} from './banner-initial.component';/*import { BannerComponent } from './banner.component';describe('BannerComponent', () => {*/describe('BannerComponent (initial CLI generated)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeDefined(); });});describe('BannerComponent (minimal)', () => { it('should create', () => { TestBed.configureTestingModule({imports: [BannerComponent]}); const fixture = TestBed.createComponent(BannerComponent); const component = fixture.componentInstance; expect(component).toBeDefined(); });});describe('BannerComponent (with beforeEach)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; beforeEach(() => { TestBed.configureTestingModule({imports: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeDefined(); }); it('should contain "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; expect(bannerElement.textContent).toContain('banner works!'); }); it('should have <p> with "banner works!"', () => { const bannerElement: HTMLElement = fixture.nativeElement; const p = bannerElement.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.nativeElement', () => { const bannerDe: DebugElement = fixture.debugElement; const bannerEl: HTMLElement = bannerDe.nativeElement; const p = bannerEl.querySelector('p')!; expect(p.textContent).toEqual('banner works!'); }); it('should find the <p> with fixture.debugElement.query(By.css)', () => { const bannerDe: DebugElement = fixture.debugElement; const paragraphDe = bannerDe.query(By.css('p')); const p: HTMLElement = paragraphDe.nativeElement; expect(p.textContent).toEqual('banner works!'); });}); Some noteworthy observations: * The `By.css()` static method selects `DebugElement` nodes with a [standard CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors "CSS"). * 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()`. --- ## Page: https://angular.dev/guide/testing/components-scenarios This guide explores common component testing use cases. ## [Component binding](https://angular.dev/#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. import {Component, signal} from '@angular/core';@Component({ selector: 'app-banner', template: '<h1>{{title()}}</h1>', styles: ['h1 { color: green; font-size: 350%}'],})export class BannerComponent { title = signal('Test Tour of Heroes');} As minimal as this is, you decide to add a test to confirm that component actually displays the right content where you think it should. ### [Query for the `<h1>`](https://angular.dev/#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. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (inline template)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [BannerComponent], }); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); it('no title in the DOM after createComponent()', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display original title after detectChanges()', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); });}); ### [`createComponent()` does not bind data](https://angular.dev/#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: import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (inline template)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [BannerComponent], }); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); it('no title in the DOM after createComponent()', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display original title after detectChanges()', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); });}); _That test fails_ with the message: expected '' to contain 'Test Tour of Heroes'. Binding happens when Angular performs **change detection**. In production, change detection kicks in automatically when Angular creates a component or the user enters a keystroke, for example. The `TestBed.createComponent` does not trigger change detection by default; a fact confirmed in the revised test: import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (inline template)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [BannerComponent], }); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); it('no title in the DOM after createComponent()', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display original title after detectChanges()', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); });}); ### [`detectChanges()`](https://angular.dev/#detectchanges) You can tell the `TestBed` to perform data binding by calling `fixture.detectChanges()`. Only then does the `<h1>` have the expected title. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (inline template)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [BannerComponent], }); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); it('no title in the DOM after createComponent()', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display original title after detectChanges()', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); });}); Delayed change detection is intentional and useful. It gives the tester an opportunity to inspect and change the state of the component _before Angular initiates data binding and calls [lifecycle hooks](https://angular.dev/guide/components/lifecycle)_. Here's another test that changes the component's `title` property _before_ calling `fixture.detectChanges()`. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (inline template)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [BannerComponent], }); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); it('no title in the DOM after createComponent()', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display original title after detectChanges()', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); });}); ### [Automatic change detection](https://angular.dev/#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: import {ComponentFixtureAutoDetect} from '@angular/core/testing';import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (AutoChangeDetect)', () => { let comp: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: ComponentFixtureAutoDetect, useValue: true}], }); fixture = TestBed.createComponent(BannerComponent); comp = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); it('should display original title', () => { // Hooray! No `fixture.detectChanges()` needed expect(h1.textContent).toContain(comp.title); }); it('should still see original title after comp.title change', async () => { const oldTitle = comp.title; const newTitle = 'Test Title'; comp.title.set(newTitle); // Displayed title is old because Angular didn't yet run change detection expect(h1.textContent).toContain(oldTitle); await fixture.whenStable(); expect(h1.textContent).toContain(newTitle); }); it('should display updated title after detectChanges', () => { comp.title.set('Test Title'); fixture.detectChanges(); // detect changes explicitly expect(h1.textContent).toContain(comp.title); });}); Then add it to the `providers` array of the testing module configuration: import {ComponentFixtureAutoDetect} from '@angular/core/testing';import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (AutoChangeDetect)', () => { let comp: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: ComponentFixtureAutoDetect, useValue: true}], }); fixture = TestBed.createComponent(BannerComponent); comp = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); it('should display original title', () => { // Hooray! No `fixture.detectChanges()` needed expect(h1.textContent).toContain(comp.title); }); it('should still see original title after comp.title change', async () => { const oldTitle = comp.title; const newTitle = 'Test Title'; comp.title.set(newTitle); // Displayed title is old because Angular didn't yet run change detection expect(h1.textContent).toContain(oldTitle); await fixture.whenStable(); expect(h1.textContent).toContain(newTitle); }); it('should display updated title after detectChanges', () => { comp.title.set('Test Title'); fixture.detectChanges(); // detect changes explicitly expect(h1.textContent).toContain(comp.title); });}); **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 `provideExperimentalZonelessChangeDetection` and turning it off is not recommended. Here are three tests that illustrate how automatic change detection works. import {ComponentFixtureAutoDetect} from '@angular/core/testing';import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner.component';describe('BannerComponent (AutoChangeDetect)', () => { let comp: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: ComponentFixtureAutoDetect, useValue: true}], }); fixture = TestBed.createComponent(BannerComponent); comp = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); it('should display original title', () => { // Hooray! No `fixture.detectChanges()` needed expect(h1.textContent).toContain(comp.title); }); it('should still see original title after comp.title change', async () => { const oldTitle = comp.title; const newTitle = 'Test Title'; comp.title.set(newTitle); // Displayed title is old because Angular didn't yet run change detection expect(h1.textContent).toContain(oldTitle); await fixture.whenStable(); expect(h1.textContent).toContain(newTitle); }); it('should display updated title after detectChanges', () => { comp.title.set('Test Title'); fixture.detectChanges(); // detect changes explicitly expect(h1.textContent).toContain(comp.title); });}); 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()`](https://angular.dev/#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. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} ## [Component with external files](https://angular.dev/#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. import {Component} from '@angular/core';@Component({ selector: 'app-banner', templateUrl: './banner-external.component.html', styleUrls: ['./banner-external.component.css'],})export class BannerComponent { title = 'Test Tour of Heroes';} 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: Error: This test module uses the component BannerComponentwhich 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](https://angular.dev/#calling-compilecomponents) section. ## [Component with a dependency](https://angular.dev/#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`: import {Component, inject, OnInit, signal} from '@angular/core';import {UserService} from '../model/user.service';@Component({ selector: 'app-welcome', template: '<h3 class="welcome"><i>{{welcome()}}</i></h3>',})export class WelcomeComponent { welcome = signal(''); private userService = inject(UserService); constructor() { this.welcome.set( this.userService.isLoggedIn() ? 'Welcome, ' + this.userService.user().name : 'Please log in.', ); }} The `WelcomeComponent` has decision logic that interacts with the service, logic that makes this component worth testing. ### [Provide service test doubles](https://angular.dev/#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](https://angular.dev/#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`. import {ComponentFixture, inject, TestBed} from '@angular/core/testing';import {UserService} from '../model/user.service';import {WelcomeComponent} from './welcome.component';class MockUserService { isLoggedIn = true; user = {name: 'Test User'};}describe('WelcomeComponent', () => { let comp: WelcomeComponent; let fixture: ComponentFixture<WelcomeComponent>; let componentUserService: UserService; // the actually injected service let userService: UserService; // the TestBed injected service let el: HTMLElement; // the DOM element with the welcome message beforeEach(() => { fixture = TestBed.createComponent(WelcomeComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService); componentUserService = userService; // UserService from the root injector userService = TestBed.inject(UserService); // get the "welcome" element by CSS selector (e.g., by class name) el = fixture.nativeElement.querySelector('.welcome'); }); it('should welcome the user', async () => { await fixture.whenStable(); const content = el.textContent; expect(content).withContext('"Welcome ..."').toContain('Welcome'); expect(content).withContext('expected name').toContain('Test User'); }); it('should welcome "Bubba"', async () => { userService.user.set({name: 'Bubba'}); // welcome message hasn't been shown yet await fixture.whenStable(); expect(el.textContent).toContain('Bubba'); }); it('should request login if not logged in', async () => { userService.isLoggedIn.set(false); // welcome message hasn't been shown yet await fixture.whenStable(); const content = el.textContent; expect(content).withContext('not welcomed').not.toContain('Welcome'); expect(content) .withContext('"log in"') .toMatch(/log in/i); }); it("should inject the component's UserService instance", inject( [UserService], (service: UserService) => { expect(service).toBe(componentUserService); }, )); it('TestBed and Component UserService should be the same', () => { expect(userService).toBe(componentUserService); });}); **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()`](https://angular.dev/#testbedinject) 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: import {ComponentFixture, inject, TestBed} from '@angular/core/testing';import {UserService} from '../model/user.service';import {WelcomeComponent} from './welcome.component';class MockUserService { isLoggedIn = true; user = {name: 'Test User'};}describe('WelcomeComponent', () => { let comp: WelcomeComponent; let fixture: ComponentFixture<WelcomeComponent>; let componentUserService: UserService; // the actually injected service let userService: UserService; // the TestBed injected service let el: HTMLElement; // the DOM element with the welcome message beforeEach(() => { fixture = TestBed.createComponent(WelcomeComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService); componentUserService = userService; // UserService from the root injector userService = TestBed.inject(UserService); // get the "welcome" element by CSS selector (e.g., by class name) el = fixture.nativeElement.querySelector('.welcome'); }); it('should welcome the user', async () => { await fixture.whenStable(); const content = el.textContent; expect(content).withContext('"Welcome ..."').toContain('Welcome'); expect(content).withContext('expected name').toContain('Test User'); }); it('should welcome "Bubba"', async () => { userService.user.set({name: 'Bubba'}); // welcome message hasn't been shown yet await fixture.whenStable(); expect(el.textContent).toContain('Bubba'); }); it('should request login if not logged in', async () => { userService.isLoggedIn.set(false); // welcome message hasn't been shown yet await fixture.whenStable(); const content = el.textContent; expect(content).withContext('not welcomed').not.toContain('Welcome'); expect(content) .withContext('"log in"') .toMatch(/log in/i); }); it("should inject the component's UserService instance", inject( [UserService], (service: UserService) => { expect(service).toBe(componentUserService); }, )); it('TestBed and Component UserService should be the same', () => { expect(userService).toBe(componentUserService); });}); **HELPFUL:** For a use case in which `TestBed.inject()` does not work, see the [_Override component providers_](https://angular.dev/#override-component-providers) section that explains when and why you must get the service from the component's injector instead. ### [Final setup and tests](https://angular.dev/#final-setup-and-tests) Here's the complete `beforeEach()`, using `TestBed.inject()`: import {ComponentFixture, inject, TestBed} from '@angular/core/testing';import {UserService} from '../model/user.service';import {WelcomeComponent} from './welcome.component';class MockUserService { isLoggedIn = true; user = {name: 'Test User'};}describe('WelcomeComponent', () => { let comp: WelcomeComponent; let fixture: ComponentFixture<WelcomeComponent>; let componentUserService: UserService; // the actually injected service let userService: UserService; // the TestBed injected service let el: HTMLElement; // the DOM element with the welcome message beforeEach(() => { fixture = TestBed.createComponent(WelcomeComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService); componentUserService = userService; // UserService from the root injector userService = TestBed.inject(UserService); // get the "welcome" element by CSS selector (e.g., by class name) el = fixture.nativeElement.querySelector('.welcome'); }); it('should welcome the user', async () => { await fixture.whenStable(); const content = el.textContent; expect(content).withContext('"Welcome ..."').toContain('Welcome'); expect(content).withContext('expected name').toContain('Test User'); }); it('should welcome "Bubba"', async () => { userService.user.set({name: 'Bubba'}); // welcome message hasn't been shown yet await fixture.whenStable(); expect(el.textContent).toContain('Bubba'); }); it('should request login if not logged in', async () => { userService.isLoggedIn.set(false); // welcome message hasn't been shown yet await fixture.whenStable(); const content = el.textContent; expect(content).withContext('not welcomed').not.toContain('Welcome'); expect(content) .withContext('"log in"') .toMatch(/log in/i); }); it("should inject the component's UserService instance", inject( [UserService], (service: UserService) => { expect(service).toBe(componentUserService); }, )); it('TestBed and Component UserService should be the same', () => { expect(userService).toBe(componentUserService); });}); And here are some tests: import {ComponentFixture, inject, TestBed} from '@angular/core/testing';import {UserService} from '../model/user.service';import {WelcomeComponent} from './welcome.component';class MockUserService { isLoggedIn = true; user = {name: 'Test User'};}describe('WelcomeComponent', () => { let comp: WelcomeComponent; let fixture: ComponentFixture<WelcomeComponent>; let componentUserService: UserService; // the actually injected service let userService: UserService; // the TestBed injected service let el: HTMLElement; // the DOM element with the welcome message beforeEach(() => { fixture = TestBed.createComponent(WelcomeComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService); componentUserService = userService; // UserService from the root injector userService = TestBed.inject(UserService); // get the "welcome" element by CSS selector (e.g., by class name) el = fixture.nativeElement.querySelector('.welcome'); }); it('should welcome the user', async () => { await fixture.whenStable(); const content = el.textContent; expect(content).withContext('"Welcome ..."').toContain('Welcome'); expect(content).withContext('expected name').toContain('Test User'); }); it('should welcome "Bubba"', async () => { userService.user.set({name: 'Bubba'}); // welcome message hasn't been shown yet await fixture.whenStable(); expect(el.textContent).toContain('Bubba'); }); it('should request login if not logged in', async () => { userService.isLoggedIn.set(false); // welcome message hasn't been shown yet await fixture.whenStable(); const content = el.textContent; expect(content).withContext('not welcomed').not.toContain('Welcome'); expect(content) .withContext('"log in"') .toMatch(/log in/i); }); it("should inject the component's UserService instance", inject( [UserService], (service: UserService) => { expect(service).toBe(componentUserService); }, )); it('TestBed and Component UserService should be the same', () => { expect(userService).toBe(componentUserService); });}); 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](https://angular.dev/#component-with-async-service) In this sample, the `AboutComponent` template hosts a `TwainComponent`. The `TwainComponent` displays Mark Twain quotes. import {Component, inject, OnInit, signal} from '@angular/core';import {AsyncPipe} from '@angular/common';import {sharedImports} from '../shared/shared';import {Observable, of} from 'rxjs';import {catchError, startWith} from 'rxjs/operators';import {TwainService} from './twain.service';@Component({ selector: 'twain-quote', template: ` <p class="twain"> <i>{{ quote | async }}</i> </p> <button type="button" (click)="getQuote()">Next quote</button> @if (errorMessage()) { <p class="error">{{ errorMessage() }}</p> }`, styles: ['.twain { font-style: italic; } .error { color: red; }'], imports: [AsyncPipe, sharedImports],})export class TwainComponent { errorMessage = signal(''); quote?: Observable<string>; private twainService = inject(TwainService); constructor() { this.getQuote(); } getQuote() { this.errorMessage.set(''); this.quote = this.twainService.getQuote().pipe( startWith('...'), catchError((err: any) => { this.errorMessage.set(err.message || err.toString()); return of('...'); // reset message to placeholder }), ); }} **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`. import {Component, inject, OnInit, signal} from '@angular/core';import {AsyncPipe} from '@angular/common';import {sharedImports} from '../shared/shared';import {Observable, of} from 'rxjs';import {catchError, startWith} from 'rxjs/operators';import {TwainService} from './twain.service';@Component({ selector: 'twain-quote', template: ` <p class="twain"> <i>{{ quote | async }}</i> </p> <button type="button" (click)="getQuote()">Next quote</button> @if (errorMessage()) { <p class="error">{{ errorMessage() }}</p> }`, styles: ['.twain { font-style: italic; } .error { color: red; }'], imports: [AsyncPipe, sharedImports],})export class TwainComponent { errorMessage = signal(''); quote?: Observable<string>; private twainService = inject(TwainService); constructor() { this.getQuote(); } getQuote() { this.errorMessage.set(''); this.quote = this.twainService.getQuote().pipe( startWith('...'), catchError((err: any) => { this.errorMessage.set(err.message || err.toString()); return of('...'); // reset message to placeholder }), ); }} 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](https://angular.dev/#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: import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); Focus on the spy. import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); 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()`](https://angular.dev/#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`. import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); **HELPFUL:** The `it()` function receives an argument of the following form. 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()`](https://angular.dev/#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](https://angular.dev/#the-tick-function) You do have to call [tick()](https://angular.dev/api/core/testing/tick) to advance the virtual clock. Calling [tick()](https://angular.dev/api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish. In this case, it waits for the observable's `setTimeout()`. The [tick()](https://angular.dev/api/core/testing/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`. import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); The [tick()](https://angular.dev/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](https://angular.dev/#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. import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); 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. import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); ### [Comparing dates inside fakeAsync()](https://angular.dev/#comparing-dates-inside-fakeasync) `fakeAsync()` simulates passage of time, which lets you calculate the difference between dates inside `fakeAsync()`. import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); ### [jasmine.clock with fakeAsync()](https://angular.dev/#jasmineclock-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`. [window as any]('__zone_symbol__fakeAsyncPatchLock') = true;import 'zone.js/testing'; import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); ### [Using the RxJS scheduler inside fakeAsync()](https://angular.dev/#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. import {fakeAsync, tick, waitForAsync} from '@angular/core/testing';import {interval, of} from 'rxjs';import {delay, take} from 'rxjs/operators';describe('Angular async helper', () => { describe('async', () => { let actuallyDone = false; beforeEach(() => { actuallyDone = false; }); afterEach(() => { expect(actuallyDone).withContext('actuallyDone should be true').toBe(true); }); it('should run normal test', () => { actuallyDone = true; }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { actuallyDone = true; done(); }, 0); }); it('should run async test with task', waitForAsync(() => { setTimeout(() => { actuallyDone = true; }, 0); })); it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); it('should run async test with successful promise', waitForAsync(() => { const p = new Promise((resolve) => { setTimeout(resolve, 10); }); p.then(() => { actuallyDone = true; }); })); it('should run async test with failed promise', waitForAsync(() => { const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); p.catch(() => { actuallyDone = true; }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), complete: done, }); }); it('should run async test with successful delayed Observable', waitForAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); })); it('should run async test with successful delayed Observable', fakeAsync(() => { const source = of(true).pipe(delay(10)); source.subscribe({ next: (val) => (actuallyDone = true), error: (err) => fail(err), }); tick(10); })); }); describe('fakeAsync', () => { it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; setTimeout(() => { called = true; }, 100); tick(100); expect(called).toBe(true); })); it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0); // the nested timeout will also be triggered expect(callback).toHaveBeenCalled(); })); it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); tick(0, {processNewMacroTasksSynchronously: false}); // the nested timeout will not be triggered expect(callback).not.toHaveBeenCalled(); tick(0); expect(callback).toHaveBeenCalled(); })); it('should get Date diff correctly in fakeAsync', fakeAsync(() => { const start = Date.now(); tick(100); const end = Date.now(); expect(end - start).toBe(100); })); it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { // need to add `import 'zone.js/plugins/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = ''; of('hello') .pipe(delay(1000)) .subscribe((v) => { result = v; }); expect(result).toBe(''); tick(1000); expect(result).toBe('hello'); const start = new Date().getTime(); let dateDiff = 0; interval(1000) .pipe(take(2)) .subscribe(() => (dateDiff = new Date().getTime() - start)); tick(1000); expect(dateDiff).toBe(1000); tick(1000); expect(dateDiff).toBe(2000); })); }); describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/testing beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; setTimeout(() => { called = true; }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); }); describe('test jsonp', () => { function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/testing it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise<void>((res) => { jsonp('localhost:8080/jsonp', () => { // success callback and resolve the promise finished = true; res(); }); }).then(() => { // async will wait until promise.then is called // if __zone_symbol__supportWaitUnResolvedChainedPromise is set expect(finished).toBe(true); }); })); });}); ### [Support more macroTasks](https://angular.dev/#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. import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {CanvasComponent} from './canvas.component';describe('CanvasComponent', () => { beforeEach(() => { (window as any).__zone_symbol__FakeAsyncTestMacroTask = [ { source: 'HTMLCanvasElement.toBlob', callbackArgs: [{size: 200}], }, ]; }); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [CanvasComponent], }).compileComponents(); }); it('should be able to generate blob data from canvas', fakeAsync(() => { const fixture = TestBed.createComponent(CanvasComponent); const canvasComp = fixture.componentInstance; fixture.detectChanges(); expect(canvasComp.blobSize).toBe(0); tick(); expect(canvasComp.blobSize).toBeGreaterThan(0); }));}); // Import patch to make async `HTMLCanvasElement` methods (such as `.toBlob()`) Zone.js-aware.// Either import in `polyfills.ts` (if used in more than one places in the app) or in the component// file using `HTMLCanvasElement` (if it is only used in a single file).import 'zone.js/plugins/zone-patch-canvas';import {Component, AfterViewInit, ViewChild, ElementRef} from '@angular/core';@Component({ selector: 'sample-canvas', template: '<canvas #sampleCanvas width="200" height="200"></canvas>',})export class CanvasComponent implements AfterViewInit { blobSize = 0; @ViewChild('sampleCanvas') sampleCanvas!: ElementRef; ngAfterViewInit() { const canvas: HTMLCanvasElement = this.sampleCanvas.nativeElement; const context = canvas.getContext('2d')!; context.clearRect(0, 0, 200, 200); context.fillStyle = '#FF1122'; context.fillRect(0, 0, 200, 200); canvas.toBlob((blob) => { this.blobSize = blob?.size ?? 0; }); }} If you want to support such a case, you need to define the macro task you want to support in `beforeEach()`. For example: import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {CanvasComponent} from './canvas.component';describe('CanvasComponent', () => { beforeEach(() => { (window as any).__zone_symbol__FakeAsyncTestMacroTask = [ { source: 'HTMLCanvasElement.toBlob', callbackArgs: [{size: 200}], }, ]; }); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [CanvasComponent], }).compileComponents(); }); it('should be able to generate blob data from canvas', fakeAsync(() => { const fixture = TestBed.createComponent(CanvasComponent); const canvasComp = fixture.componentInstance; fixture.detectChanges(); expect(canvasComp.blobSize).toBe(0); tick(); expect(canvasComp.blobSize).toBeGreaterThan(0); }));}); **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>`): // Import patch to make async `HTMLCanvasElement` methods (such as `.toBlob()`) Zone.js-aware.// Either import in `polyfills.ts` (if used in more than one places in the app) or in the component// file using `HTMLCanvasElement` (if it is only used in a single file).import 'zone.js/plugins/zone-patch-canvas';import {Component, AfterViewInit, ViewChild, ElementRef} from '@angular/core';@Component({ selector: 'sample-canvas', template: '<canvas #sampleCanvas width="200" height="200"></canvas>',})export class CanvasComponent implements AfterViewInit { blobSize = 0; @ViewChild('sampleCanvas') sampleCanvas!: ElementRef; ngAfterViewInit() { const canvas: HTMLCanvasElement = this.sampleCanvas.nativeElement; const context = canvas.getContext('2d')!; context.clearRect(0, 0, 200, 200); context.fillStyle = '#FF1122'; context.fillRect(0, 0, 200, 200); canvas.toBlob((blob) => { this.blobSize = blob?.size ?? 0; }); }} ### [Async observables](https://angular.dev/#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. import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); ### [Async observable helpers](https://angular.dev/#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. /* * Mock async observables that return asynchronously. * The observable either emits once and completes or errors. * * Must call `tick()` when test with `fakeAsync()`. * * THE FOLLOWING DON'T WORK * Using `of().delay()` triggers TestBed errors; * see https://github.com/angular/angular/issues/10127 . * * Using `asap` scheduler - as in `of(value, asap)` - doesn't work either. */import {defer} from 'rxjs';/** * Create async observable that emits-once and completes * after a JS engine turn */export function asyncData<T>(data: T) { return defer(() => Promise.resolve(data));}/** * Create async observable error that errors * after a JS engine turn */export function asyncError<T>(errorObject: any) { return defer(() => Promise.reject(errorObject));} 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. /* * Mock async observables that return asynchronously. * The observable either emits once and completes or errors. * * Must call `tick()` when test with `fakeAsync()`. * * THE FOLLOWING DON'T WORK * Using `of().delay()` triggers TestBed errors; * see https://github.com/angular/angular/issues/10127 . * * Using `asap` scheduler - as in `of(value, asap)` - doesn't work either. */import {defer} from 'rxjs';/** * Create async observable that emits-once and completes * after a JS engine turn */export function asyncData<T>(data: T) { return defer(() => Promise.resolve(data));}/** * Create async observable error that errors * after a JS engine turn */export function asyncError<T>(errorObject: any) { return defer(() => Promise.reject(errorObject));} ### [More async tests](https://angular.dev/#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. import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); 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()](https://angular.dev/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()`](https://angular.dev/#async-test-without-fakeasync) Here's the previous `fakeAsync()` test, re-written with the `async`. import {fakeAsync, ComponentFixture, TestBed, tick, waitForAsync} from '@angular/core/testing';import {asyncData, asyncError} from '../../testing';import {Subject, defer, of, throwError} from 'rxjs';import {last} from 'rxjs/operators';import {TwainComponent} from './twain.component';import {TwainService} from './twain.service';describe('TwainComponent', () => { let component: TwainComponent; let fixture: ComponentFixture<TwainComponent>; let getQuoteSpy: jasmine.Spy; let quoteEl: HTMLElement; let testQuote: string; // Helper function to get the error message element value // An *ngIf keeps it out of the DOM until there is an error const errorMessage = () => { const el = fixture.nativeElement.querySelector('.error'); return el ? el.textContent : null; }; beforeEach(() => { TestBed.configureTestingModule({ imports: [TwainComponent], providers: [TwainService], }); testQuote = 'Test Quote'; // Create a fake TwainService object with a `getQuote()` spy const twainService = TestBed.inject(TwainService); // Make the spy return a synchronous Observable with the test data getQuoteSpy = spyOn(twainService, 'getQuote').and.returnValue(of(testQuote)); fixture = TestBed.createComponent(TwainComponent); fixture.autoDetectChanges(); component = fixture.componentInstance; quoteEl = fixture.nativeElement.querySelector('.twain'); }); describe('when test with synchronous observable', () => { it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); // The quote would not be immediately available if the service were truly async. it('should show quote after component initialized', async () => { await fixture.whenStable(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); // The error would not be immediately available if the service were truly async. // Use `fakeAsync` because the component error calls `setTimeout` it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an error observable after a timeout getQuoteSpy.and.returnValue( defer(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject('TwainService test failure'); }); }); }), ); fixture.detectChanges(); // onInit() // sync spy errors immediately after init tick(); // flush the setTimeout() fixture.detectChanges(); // update errorMessage within setTimeout() expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); }); describe('when test with asynchronous observable', () => { beforeEach(() => { // Simulate delayed observable values with the `asyncData()` helper getQuoteSpy.and.returnValue(asyncData(testQuote)); }); it('should not show quote before OnInit', () => { expect(quoteEl.textContent).withContext('nothing displayed').toBe(''); expect(errorMessage()).withContext('should not show error element').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote not yet called').toBe(false); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote // so should show the start value, '...' expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); expect(errorMessage()).withContext('should not show error').toBeNull(); expect(getQuoteSpy.calls.any()).withContext('getQuote called').toBe(true); }); it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); tick(); // flush the observable to get the quote fixture.detectChanges(); // update view expect(quoteEl.textContent).withContext('should show quote').toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); })); it('should show quote after getQuote (async)', async () => { fixture.detectChanges(); // ngOnInit() expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); await fixture.whenStable(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).withContext('should not show error').toBeNull(); }); it('should display error when TwainService fails', fakeAsync(() => { // tell spy to return an async error observable getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure')); fixture.detectChanges(); tick(); // component shows error after a setTimeout() fixture.detectChanges(); // update error message expect(errorMessage()) .withContext('should display error') .toMatch(/test failure/); expect(quoteEl.textContent).withContext('should show placeholder').toBe('...'); })); });}); ### [`whenStable`](https://angular.dev/#whenstable) The test must wait for the `getQuote()` observable to emit the next quote. Instead of calling [tick()](https://angular.dev/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](https://angular.dev/#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: <h2 highlight>{{ title }}</h2><div class="grid grid-pad"> @for (hero of heroes; track hero) { <dashboard-hero class="col-1-4" [hero]="hero" (selected)="gotoDetail($event)" > </dashboard-hero> }</div> The `DashboardHeroComponent` appears in an `*ngFor` repeater, 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: import {Component, input, output} from '@angular/core';import {UpperCasePipe} from '@angular/common';import {Hero} from '../model/hero';@Component({ selector: 'dashboard-hero', template: ` <button type="button" (click)="click()" class="hero"> {{ hero().name | uppercase }} </button> `, styleUrls: ['./dashboard-hero.component.css'], imports: [UpperCasePipe],})export class DashboardHeroComponent { hero = input.required<Hero>(); selected = output<Hero>(); click() { this.selected.emit(this.hero()); }} While testing a component this simple has little intrinsic value, it's worth knowing how. Use one of these approaches: * Test it as used by `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](https://angular.dev/#test-dashboardherocomponent-standalone) Here's the meat of the spec file setup. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} 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. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} Because the [template](https://angular.dev/#dashboard-hero-component) passes the hero name through the Angular `UpperCasePipe`, the test must match the element value with the upper-cased name. ### [Clicking](https://angular.dev/#clicking) Clicking the hero should raise a `selected` event that the host component (`DashboardComponent` presumably) can hear: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} 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`](https://angular.dev/#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. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} 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](https://angular.dev/#click-the-element) The following test alternative calls the native element's own `click()` method, which is perfectly fine for _this component_. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} ### [`click()` helper](https://angular.dev/#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: import {DebugElement} from '@angular/core';import {ComponentFixture, tick} from '@angular/core/testing';export * from './async-observable-helpers';export * from './jasmine-matchers';///// Short utilities //////** Wait a tick, then detect changes */export function advance(f: ComponentFixture<any>): void { tick(); f.detectChanges();}// See https://developer.mozilla.org/docs/Web/API/MouseEvent/button/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */export const ButtonClickEvents = { left: {button: 0}, right: {button: 2},};/** Simulate element click. Defaults to mouse left-button click event. */export function click( el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left,): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); }} The first parameter is the _element-to-click_. If you want, pass a custom event object as the second parameter. The default is a partial [left-button mouse event object](https://developer.mozilla.org/docs/Web/API/MouseEvent/button) accepted by many handlers including the `RouterLink` 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. import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} ## [Component inside a test host](https://angular.dev/#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? import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} The test host sets the component's `hero` input property with its test hero. It binds the component's `selected` event with its `onSelected` handler, which records the emitted hero in its `selectedHero` property. Later, the tests will be able to check `selectedHero` to verify that the `DashboardHeroComponent.selected` event emitted the expected hero. The setup for the `test-host` tests is similar to the setup for the stand-alone tests: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} This testing module configuration shows three important differences: * It _imports_ both the `DashboardHeroComponent` and the `TestHostComponent` * 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: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} 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](https://angular.dev/#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. import {provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {NO_ERRORS_SCHEMA} from '@angular/core';import {TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {NavigationEnd, provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {firstValueFrom} from 'rxjs';import {filter} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {HeroService} from '../model/hero.service';import {getTestHeroes} from '../model/testing/test-heroes';import {DashboardComponent} from './dashboard.component';import {appConfig} from '../app.config';import {HeroDetailComponent} from '../hero/hero-detail.component';beforeEach(addMatchers);let comp: DashboardComponent;let harness: RouterTestingHarness;//////// Deep ////////////////describe('DashboardComponent (deep)', () => { compileAndCreate(); tests(clickForDeep); function clickForDeep() { // get first <div class="hero"> const heroEl: HTMLElement = harness.routeNativeElement!.querySelector('.hero')!; click(heroEl); return firstValueFrom( TestBed.inject(Router).events.pipe(filter((e) => e instanceof NavigationEnd)), ); }});//////// Shallow ////////////////describe('DashboardComponent (shallow)', () => { beforeEach(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [DashboardComponent, HeroDetailComponent], providers: [provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}])], schemas: [NO_ERRORS_SCHEMA], }), ); }); compileAndCreate(); tests(clickForShallow); function clickForShallow() { // get first <dashboard-hero> DebugElement const heroDe = harness.routeDebugElement!.query(By.css('dashboard-hero')); heroDe.triggerEventHandler('selected', comp.heroes[0]); return Promise.resolve(); }});/** Add TestBed providers, compile, and create DashboardComponent */function compileAndCreate() { beforeEach(async () => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [DashboardComponent], providers: [ provideRouter([{path: '**', component: DashboardComponent}]), provideHttpClient(), provideHttpClientTesting(), HeroService, ], }), ); harness = await RouterTestingHarness.create(); comp = await harness.navigateByUrl('/', DashboardComponent); TestBed.inject(HttpTestingController).expectOne('api/heroes').flush(getTestHeroes()); });}/** * The (almost) same tests for both. * Only change: the way that the first hero is clicked */function tests(heroClick: () => Promise<unknown>) { describe('after get dashboard heroes', () => { let router: Router; // Trigger component so it gets heroes and binds to them beforeEach(waitForAsync(() => { router = TestBed.inject(Router); harness.detectChanges(); // runs ngOnInit -> getHeroes })); it('should HAVE heroes', () => { expect(comp.heroes.length) .withContext('should have heroes after service promise resolves') .toBeGreaterThan(0); }); it('should DISPLAY heroes', () => { // Find and examine the displayed heroes // Look for them in the DOM by css class const heroes = harness.routeNativeElement!.querySelectorAll('dashboard-hero'); expect(heroes.length).withContext('should display 4 heroes').toBe(4); }); it('should tell navigate when hero clicked', async () => { await heroClick(); // trigger click on first inner <div class="hero"> // expecting to navigate to id of the component's first hero const id = comp.heroes[0].id; expect(TestBed.inject(Router).url) .withContext('should nav to HeroDetail for first hero') .toEqual(`/heroes/${id}`); }); });} The following test clicks the displayed hero and confirms that we navigate to the expected URL. import {provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {NO_ERRORS_SCHEMA} from '@angular/core';import {TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {NavigationEnd, provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {firstValueFrom} from 'rxjs';import {filter} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {HeroService} from '../model/hero.service';import {getTestHeroes} from '../model/testing/test-heroes';import {DashboardComponent} from './dashboard.component';import {appConfig} from '../app.config';import {HeroDetailComponent} from '../hero/hero-detail.component';beforeEach(addMatchers);let comp: DashboardComponent;let harness: RouterTestingHarness;//////// Deep ////////////////describe('DashboardComponent (deep)', () => { compileAndCreate(); tests(clickForDeep); function clickForDeep() { // get first <div class="hero"> const heroEl: HTMLElement = harness.routeNativeElement!.querySelector('.hero')!; click(heroEl); return firstValueFrom( TestBed.inject(Router).events.pipe(filter((e) => e instanceof NavigationEnd)), ); }});//////// Shallow ////////////////describe('DashboardComponent (shallow)', () => { beforeEach(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [DashboardComponent, HeroDetailComponent], providers: [provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}])], schemas: [NO_ERRORS_SCHEMA], }), ); }); compileAndCreate(); tests(clickForShallow); function clickForShallow() { // get first <dashboard-hero> DebugElement const heroDe = harness.routeDebugElement!.query(By.css('dashboard-hero')); heroDe.triggerEventHandler('selected', comp.heroes[0]); return Promise.resolve(); }});/** Add TestBed providers, compile, and create DashboardComponent */function compileAndCreate() { beforeEach(async () => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [DashboardComponent], providers: [ provideRouter([{path: '**', component: DashboardComponent}]), provideHttpClient(), provideHttpClientTesting(), HeroService, ], }), ); harness = await RouterTestingHarness.create(); comp = await harness.navigateByUrl('/', DashboardComponent); TestBed.inject(HttpTestingController).expectOne('api/heroes').flush(getTestHeroes()); });}/** * The (almost) same tests for both. * Only change: the way that the first hero is clicked */function tests(heroClick: () => Promise<unknown>) { describe('after get dashboard heroes', () => { let router: Router; // Trigger component so it gets heroes and binds to them beforeEach(waitForAsync(() => { router = TestBed.inject(Router); harness.detectChanges(); // runs ngOnInit -> getHeroes })); it('should HAVE heroes', () => { expect(comp.heroes.length) .withContext('should have heroes after service promise resolves') .toBeGreaterThan(0); }); it('should DISPLAY heroes', () => { // Find and examine the displayed heroes // Look for them in the DOM by css class const heroes = harness.routeNativeElement!.querySelectorAll('dashboard-hero'); expect(heroes.length).withContext('should display 4 heroes').toBe(4); }); it('should tell navigate when hero clicked', async () => { await heroClick(); // trigger click on first inner <div class="hero"> // expecting to navigate to id of the component's first hero const id = comp.heroes[0].id; expect(TestBed.inject(Router).url) .withContext('should nav to HeroDetail for first hero') .toEqual(`/heroes/${id}`); }); });} ## [Routed components](https://angular.dev/#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`: import {Component, inject} from '@angular/core';import {ActivatedRoute, Router, RouterLink} from '@angular/router';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailService} from './hero-detail.service';@Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: ['./hero-detail.component.css'], providers: [HeroDetailService], imports: [sharedImports, RouterLink],})export class HeroDetailComponent { private heroDetailService = inject(HeroDetailService); private route = inject(ActivatedRoute); private router = inject(Router); hero!: Hero; constructor() { // get hero when `id` param changes this.route.paramMap.subscribe((pmap) => this.getHero(pmap.get('id'))); } private getHero(id: string | null): void { // when no id or id===0, create new blank hero if (!id) { this.hero = {id: 0, name: ''} as Hero; return; } this.heroDetailService.getHero(id).subscribe((hero) => { if (hero) { this.hero = hero; } else { this.gotoList(); // id not found; navigate to list } }); } save(): void { this.heroDetailService.saveHero(this.hero).subscribe(() => this.gotoList()); } cancel() { this.gotoList(); } gotoList() { this.router.navigate(['../'], {relativeTo: this.route}); }} 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. import {Component, inject} from '@angular/core';import {ActivatedRoute, Router, RouterLink} from '@angular/router';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailService} from './hero-detail.service';@Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: ['./hero-detail.component.css'], providers: [HeroDetailService], imports: [sharedImports, RouterLink],})export class HeroDetailComponent { private heroDetailService = inject(HeroDetailService); private route = inject(ActivatedRoute); private router = inject(Router); hero!: Hero; constructor() { // get hero when `id` param changes this.route.paramMap.subscribe((pmap) => this.getHero(pmap.get('id'))); } private getHero(id: string | null): void { // when no id or id===0, create new blank hero if (!id) { this.hero = {id: 0, name: ''} as Hero; return; } this.heroDetailService.getHero(id).subscribe((hero) => { if (hero) { this.hero = hero; } else { this.gotoList(); // id not found; navigate to list } }); } save(): void { this.heroDetailService.saveHero(this.hero).subscribe(() => this.gotoList()); } cancel() { this.gotoList(); } gotoList() { this.router.navigate(['../'], {relativeTo: this.route}); }} Tests can explore how the `HeroDetailComponent` responds to different `id` parameter values by navigating to different routes. ### [Testing with the `RouterTestingHarness`](https://angular.dev/#testing-with-the-routertestingharness) Here's a test demonstrating the component's behavior when the observed `id` refers to an existing hero: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} **HELPFUL:** In the following section, the `createComponent()` method and `page` object are discussed. Rely on your intuition for now. When the `id` cannot be found, the component should re-route to the `HeroListComponent`. The test suite setup provided the same router harness [described above](https://angular.dev/#routing-component). This test expects the component to try to navigate to the `HeroListComponent`. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} ## [Nested component tests](https://angular.dev/#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. <app-banner></app-banner><app-welcome></app-welcome><nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/heroes">Heroes</a> <a routerLink="/about">About</a></nav><router-outlet></router-outlet> 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](https://angular.dev/#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. import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} The stub selectors match the selectors for the corresponding real components. But their templates and classes are empty. Then declare them in the `TestBed` configuration next to the components, directives, and pipes that need to be real. import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} The `AppComponent` is the test subject, so of course you declare the real version. The rest are stubs. In the second approach, add `NO_ERRORS_SCHEMA` to the `TestBed.schemas` metadata. import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} 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](https://angular.dev/#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. import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} 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](https://angular.dev/#bydirective-and-injected-directives) A little more setup triggers the initial data binding and gets references to the navigation links: import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} 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: <app-banner></app-banner><app-welcome></app-welcome><nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/heroes">Heroes</a> <a routerLink="/about">About</a></nav><router-outlet></router-outlet> Here are some tests that confirm those links are wired to the `routerLink` directives as expected: import {Component, DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {provideRouter, Router, RouterLink} from '@angular/router';import {AppComponent} from './app.component';import {appConfig} from './app.config';import {UserService} from './model';@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent {}@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}let comp: AppComponent;let fixture: ComponentFixture<AppComponent>;describe('AppComponent & TestModule', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent, ], providers: [provideRouter([]), UserService], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});//////// Testing w/ NO_ERRORS_SCHEMA //////describe('AppComponent & NO_ERRORS_SCHEMA', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [ AppComponent, BannerStubComponent, RouterLink, ], providers: [provideRouter([]), UserService], schemas: [NO_ERRORS_SCHEMA], }), ) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); tests();});function tests() { let routerLinks: RouterLink[]; let linkDes: DebugElement[]; beforeEach(() => { fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement.queryAll(By.directive(RouterLink)); // get attached link directive instances // using each DebugElement's injector routerLinks = linkDes.map((de) => de.injector.get(RouterLink)); }); it('can instantiate the component', () => { expect(comp).not.toBeNull(); }); it('can get RouterLinks from template', () => { expect(routerLinks.length).withContext('should have 3 routerLinks').toBe(3); expect(routerLinks[0].href).toBe('/dashboard'); expect(routerLinks[1].href).toBe('/heroes'); expect(routerLinks[2].href).toBe('/about'); }); it('can click Heroes link in template', fakeAsync(() => { const heroesLinkDe = linkDes[1]; // heroes link DebugElement TestBed.inject(Router).resetConfig([{path: '**', children: []}]); heroesLinkDe.triggerEventHandler('click', {button: 0}); tick(); fixture.detectChanges(); expect(TestBed.inject(Router).url).toBe('/heroes'); }));} ## [Use a `page` object](https://angular.dev/#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. @if (hero) { <div> <h2> <span>{{ hero.name | titlecase }}</span> Details </h2> <div><span>id: </span>{{ hero.id }}</div> <div> <label for="name">name: </label> <input id="name" [(ngModel)]="hero.name" placeholder="name" /> </div> <button type="button" (click)="save()">Save</button> <button type="button" (click)="cancel()">Cancel</button> </div>} Tests that exercise the component need … * To wait until a hero arrives before elements appear in the DOM * A reference to the title text * A reference to the name input box to inspect and set it * References to the two buttons so they can click them Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection. Tame the complexity with a `Page` class that handles access to component properties and encapsulates the logic that sets them. Here is such a `Page` class for the `hero-detail.component.spec.ts` import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} Now the important hooks for component manipulation and inspection are neatly organized and accessible from an instance of `Page`. A `createComponent` method creates a `page` object and fills in the blanks once the `hero` arrives. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} Here are a few more `HeroDetailComponent` tests to reinforce the point. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} ## [Calling `compileComponents()`](https://angular.dev/#calling-compilecomponents) **HELPFUL:** Ignore this section if you _only_ run tests with the CLI `ng test` command because the CLI compiles the application before running the tests. If you run tests in a **non-CLI environment**, the tests might fail with a message like this one: Error: This test module uses the component BannerComponentwhich is using a "templateUrl" or "styleUrls", but they were never compiled.Please call "TestBed.compileComponents" before your test. The root of the problem is at least one of the components involved in the test specifies an external template or CSS file as the following version of the `BannerComponent` does. import {Component} from '@angular/core';@Component({ selector: 'app-banner', templateUrl: './banner-external.component.html', styleUrls: ['./banner-external.component.css'],})export class BannerComponent { title = 'Test Tour of Heroes';} The test fails when the `TestBed` tries to create the component. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner-external.component';describe('BannerComponent (external files)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; describe('setup that may fail', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }); // missing call to compileComponents() fixture = TestBed.createComponent(BannerComponent); }); it('should create', () => { expect(fixture.componentInstance).toBeDefined(); }); }); describe('Two beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); // compile template and css }); // synchronous beforeEach beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); describe('One beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); function tests() { it('no title in the DOM until manually call `detectChanges`', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); }); }}); Recall that the application hasn't been compiled. So when you call `createComponent()`, the `TestBed` compiles implicitly. That's not a problem when the source code is in memory. But the `BannerComponent` requires external files that the compiler must read from the file system, an inherently _asynchronous_ operation. If the `TestBed` were allowed to continue, the tests would run and fail mysteriously before the compiler could finish. The preemptive error message tells you to compile explicitly with `compileComponents()`. ### [`compileComponents()` is async](https://angular.dev/#compilecomponents-is-async) You must call `compileComponents()` within an asynchronous test function. **CRITICAL:** If you neglect to make the test function async (for example, forget to use `waitForAsync()` as described), you'll see this error message Error: ViewDestroyedError: Attempt to use a destroyed view A typical approach is to divide the setup logic into two separate `beforeEach()` functions: | Functions | Details | | --- | --- | | Asynchronous `beforeEach()` | Compiles the components | | Synchronous `beforeEach()` | Performs the remaining setup | ### [The async `beforeEach`](https://angular.dev/#the-async-beforeeach) Write the first async `beforeEach` like this. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner-external.component';describe('BannerComponent (external files)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; describe('setup that may fail', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }); // missing call to compileComponents() fixture = TestBed.createComponent(BannerComponent); }); it('should create', () => { expect(fixture.componentInstance).toBeDefined(); }); }); describe('Two beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); // compile template and css }); // synchronous beforeEach beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); describe('One beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); function tests() { it('no title in the DOM until manually call `detectChanges`', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); }); }}); The `TestBed.configureTestingModule()` method returns the `TestBed` class so you can chain calls to other `TestBed` static methods such as `compileComponents()`. In this example, the `BannerComponent` is the only component to compile. Other examples configure the testing module with multiple components and might import application modules that hold yet more components. Any of them could require external files. The `TestBed.compileComponents` method asynchronously compiles all components configured in the testing module. **IMPORTANT:** Do not re-configure the `TestBed` after calling `compileComponents()`. Calling `compileComponents()` closes the current `TestBed` instance to further configuration. You cannot call any more `TestBed` configuration methods, not `configureTestingModule()` nor any of the `override...` methods. The `TestBed` throws an error if you try. Make `compileComponents()` the last step before calling `TestBed.createComponent()`. ### [The synchronous `beforeEach`](https://angular.dev/#the-synchronous-beforeeach) The second, synchronous `beforeEach()` contains the remaining setup steps, which include creating the component and querying for elements to inspect. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner-external.component';describe('BannerComponent (external files)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; describe('setup that may fail', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }); // missing call to compileComponents() fixture = TestBed.createComponent(BannerComponent); }); it('should create', () => { expect(fixture.componentInstance).toBeDefined(); }); }); describe('Two beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); // compile template and css }); // synchronous beforeEach beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); describe('One beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); function tests() { it('no title in the DOM until manually call `detectChanges`', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); }); }}); Count on the test runner to wait for the first asynchronous `beforeEach` to finish before calling the second. ### [Consolidated setup](https://angular.dev/#consolidated-setup) You can consolidate the two `beforeEach()` functions into a single, async `beforeEach()`. The `compileComponents()` method returns a promise so you can perform the synchronous setup tasks _after_ compilation by moving the synchronous code after the `await` keyword, where the promise has been resolved. import {ComponentFixture, TestBed} from '@angular/core/testing';import {BannerComponent} from './banner-external.component';describe('BannerComponent (external files)', () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let h1: HTMLElement; describe('setup that may fail', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }); // missing call to compileComponents() fixture = TestBed.createComponent(BannerComponent); }); it('should create', () => { expect(fixture.componentInstance).toBeDefined(); }); }); describe('Two beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); // compile template and css }); // synchronous beforeEach beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); describe('One beforeEach', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [BannerComponent], }).compileComponents(); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; h1 = fixture.nativeElement.querySelector('h1'); }); tests(); }); function tests() { it('no title in the DOM until manually call `detectChanges`', () => { expect(h1.textContent).toEqual(''); }); it('should display original title', () => { fixture.detectChanges(); expect(h1.textContent).toContain(component.title); }); it('should display a different test title', () => { component.title = 'Test Title'; fixture.detectChanges(); expect(h1.textContent).toContain('Test Title'); }); }}); ### [`compileComponents()` is harmless](https://angular.dev/#compilecomponents-is-harmless) There's no harm in calling `compileComponents()` when it's not required. The component test file generated by the CLI calls `compileComponents()` even though it is never required when running `ng test`. The tests in this guide only call `compileComponents` when necessary. ## [Setup with module imports](https://angular.dev/#setup-with-module-imports) Earlier component tests configured the testing module with a few `declarations` like this: import {DebugElement} from '@angular/core';import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {first} from 'rxjs/operators';import {addMatchers, click} from '../../testing';import {appProviders} from '../app.config';import {Hero} from '../model/hero';import {DashboardHeroComponent} from './dashboard-hero.component';beforeEach(addMatchers);describe('DashboardHeroComponent when tested directly', () => { let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture<DashboardHeroComponent>; let heroDe: DebugElement; let heroEl: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ providers: appProviders, }); }); beforeEach(async () => { fixture = TestBed.createComponent(DashboardHeroComponent); fixture.autoDetectChanges(); comp = fixture.componentInstance; // find the hero's DebugElement and element heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero fixture.componentRef.setInput('hero', expectedHero); // wait for initial data binding await fixture.whenStable(); }); it('should display hero name in uppercase', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked (triggerEventHandler)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroDe.triggerEventHandler('click'); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (element.click)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); heroEl.click(); expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with DebugElement)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroDe); // click helper with DebugElement expect(selectedHero).toBe(expectedHero); }); it('should raise selected event when clicked (click helper with native element)', () => { let selectedHero: Hero | undefined; comp.selected.subscribe((hero: Hero) => (selectedHero = hero)); click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); });});//////////////////describe('DashboardHeroComponent when inside a test host', () => { let testHost: TestHostComponent; let fixture: ComponentFixture<TestHostComponent>; let heroEl: HTMLElement; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: appProviders, imports: [DashboardHeroComponent, TestHostComponent], }) .compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.nativeElement.querySelector('.hero'); fixture.detectChanges(); // trigger initial data binding }); it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });});////// Test Host Component //////import {Component} from '@angular/core';@Component({ imports: [DashboardHeroComponent], template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,})class TestHostComponent { hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero | undefined; onSelected(hero: Hero) { this.selectedHero = hero; }} The `DashboardComponent` is simple. It needs no help. But more complex components often depend on other components, directives, pipes, and providers and these must be added to the testing module too. Fortunately, the `TestBed.configureTestingModule` parameter parallels the metadata passed to the `@NgModule` decorator which means you can also specify `providers` and `imports`. The `HeroDetailComponent` requires a lot of help despite its small size and simple construction. In addition to the support it receives from the default testing module `CommonModule`, it needs: * `NgModel` and friends in the `FormsModule` to enable two-way data binding * The `TitleCasePipe` from the `shared` folder * The Router services * The Hero data access services One approach is to configure the testing module from the individual pieces as in this example: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} **HELPFUL:** Notice that the `beforeEach()` is asynchronous and calls `TestBed.compileComponents` because the `HeroDetailComponent` has an external template and css file. As explained in [Calling `compileComponents()`](https://angular.dev/#calling-compilecomponents), these tests could be run in a non-CLI environment where Angular would have to compile them in the browser. ### [Import a shared module](https://angular.dev/#import-a-shared-module) Because many application components need the `FormsModule` and the `TitleCasePipe`, the developer created a `SharedModule` to combine these and other frequently requested parts. The test configuration can use the `SharedModule` too as seen in this alternative setup: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} It's a bit tighter and smaller, with fewer import statements, which are not shown in this example. ### [Import a feature module](https://angular.dev/#import-a-feature-module) The `HeroDetailComponent` is part of the `HeroModule` [Feature Module](https://angular.dev/guide/ngmodules/feature-modules) that aggregates more of the interdependent pieces including the `SharedModule`. Try a test configuration that imports the `HeroModule` like this one: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} Only the _test doubles_ in the `providers` remain. Even the `HeroDetailComponent` declaration is gone. In fact, if you try to declare it, Angular will throw an error because `HeroDetailComponent` is declared in both the `HeroModule` and the `DynamicTestModule` created by the `TestBed`. **HELPFUL:** Importing the component's feature module can be the best way to configure tests when there are many mutual dependencies within the module and the module is small, as feature modules tend to be. ## [Override component providers](https://angular.dev/#override-component-providers) The `HeroDetailComponent` provides its own `HeroDetailService`. import {Component, inject} from '@angular/core';import {ActivatedRoute, Router, RouterLink} from '@angular/router';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailService} from './hero-detail.service';@Component({ selector: 'app-hero-detail', templateUrl: './hero-detail.component.html', styleUrls: ['./hero-detail.component.css'], providers: [HeroDetailService], imports: [sharedImports, RouterLink],})export class HeroDetailComponent { private heroDetailService = inject(HeroDetailService); private route = inject(ActivatedRoute); private router = inject(Router); hero!: Hero; constructor() { // get hero when `id` param changes this.route.paramMap.subscribe((pmap) => this.getHero(pmap.get('id'))); } private getHero(id: string | null): void { // when no id or id===0, create new blank hero if (!id) { this.hero = {id: 0, name: ''} as Hero; return; } this.heroDetailService.getHero(id).subscribe((hero) => { if (hero) { this.hero = hero; } else { this.gotoList(); // id not found; navigate to list } }); } save(): void { this.heroDetailService.saveHero(this.hero).subscribe(() => this.gotoList()); } cancel() { this.gotoList(); } gotoList() { this.router.navigate(['../'], {relativeTo: this.route}); }} 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`. import {inject, Injectable} from '@angular/core';import {Observable} from 'rxjs';import {map} from 'rxjs/operators';import {Hero} from '../model/hero';import {HeroService} from '../model/hero.service';@Injectable({providedIn: 'root'})export class HeroDetailService { private heroService = inject(HeroService); // Returns a clone which caller may modify safely getHero(id: number | string): Observable<Hero | null> { if (typeof id === 'string') { id = parseInt(id, 10); } return this.heroService.getHero(id).pipe( map((hero) => (hero ? Object.assign({}, hero) : null)), // clone or null ); } saveHero(hero: Hero) { return this.heroService.updateHero(hero); }} The [previous test configuration](https://angular.dev/#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: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} Notice that `TestBed.configureTestingModule` no longer provides a fake `HeroService` because it's [not needed](https://angular.dev/#spy-stub). ### [The `overrideComponent` method](https://angular.dev/#the-overridecomponent-method) Focus on the `overrideComponent` method. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} It takes two arguments: the component type to override (`HeroDetailComponent`) and an override metadata object. The [override metadata object](https://angular.dev/guide/testing/utility-apis#metadata-override-object) is a generic defined as follows: type MetadataOverride<T> = { add?: Partial<T>; remove?: Partial<T>; set?: Partial<T>;}; A metadata override object can either add-and-remove elements in metadata properties or completely reset those properties. This example resets the component's `providers` metadata. The type parameter, `T`, is the kind of metadata you'd pass to the `@Component` decorator: selector?: string;template?: string;templateUrl?: string;providers?: any[];… ### [Provide a _spy stub_ (`HeroDetailServiceSpy`)](https://angular.dev/#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: import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} ### [The override tests](https://angular.dev/#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. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} ### [More overrides](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/testing/attribute-directives An _attribute directive_ modifies the behavior of an element, component or another directive. Its name reflects the way the directive is applied: as an attribute on a host element. ## [Testing the `HighlightDirective`](https://angular.dev/#testing-the-highlightdirective) The sample application's `HighlightDirective` sets the background color of an element based on either a data bound color or a default color (lightgray). It also sets a custom property of the element (`customProperty`) to `true` for no reason other than to show that it can. import {Directive, ElementRef, inject, Input, OnChanges} from '@angular/core';@Directive({selector: '[highlight]'})/** * Set backgroundColor for the attached element to highlight color * and set the element's customProperty to true */export class HighlightDirective implements OnChanges { defaultColor = 'rgb(211, 211, 211)'; // lightgray @Input('highlight') bgColor = ''; private el = inject(ElementRef); constructor() { this.el.nativeElement.style.customProperty = true; } ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; }} It's used throughout the application, perhaps most simply in the `AboutComponent`: import {Component} from '@angular/core';import {HighlightDirective} from '../shared/highlight.directive';import {TwainComponent} from '../twain/twain.component';@Component({ template: ` <h2 highlight="skyblue">About</h2> <h3>Quote of the day:</h3> <twain-quote></twain-quote> `, imports: [TwainComponent, HighlightDirective],})export class AboutComponent {} Testing the specific use of the `HighlightDirective` within the `AboutComponent` requires only the techniques explored in the ["Nested component tests"](https://angular.dev/guide/testing/components-scenarios#nested-component-tests) section of [Component testing scenarios](https://angular.dev/guide/testing/components-scenarios). import {provideHttpClient} from '@angular/common/http';import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {UserService} from '../model';import {TwainService} from '../twain/twain.service';import {AboutComponent} from './about.component';let fixture: ComponentFixture<AboutComponent>;describe('AboutComponent (highlightDirective)', () => { beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [AboutComponent], providers: [provideHttpClient(), TwainService, UserService], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).createComponent(AboutComponent); fixture.detectChanges(); // initial binding }); it('should have skyblue <h2>', () => { const h2: HTMLElement = fixture.nativeElement.querySelector('h2'); const bgColor = h2.style.backgroundColor; expect(bgColor).toBe('skyblue'); });}); However, testing a single use case is unlikely to explore the full range of a directive's capabilities. Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage. _Class-only tests_ might be helpful, but attribute directives like this one tend to manipulate the DOM. Isolated unit tests don't touch the DOM and, therefore, do not inspire confidence in the directive's efficacy. A better solution is to create an artificial test component that demonstrates all ways to apply the directive. import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@Component({ template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [HighlightDirective, TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });});  **HELPFUL:** The `<input>` case binds the `HighlightDirective` to the name of a color value in the input box. The initial value is the word "cyan" which should be the background color of the input box. Here are some tests of this component: import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@Component({ template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [HighlightDirective, TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });}); A few techniques are noteworthy: * The `By.directive` predicate is a great way to get the elements that have this directive _when their element types are unknown_ * The [`:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not) in `By.css('h2:not([highlight])')` helps find `<h2>` elements that _do not_ have the directive. `By.css('*:not([highlight])')` finds _any_ element that does not have the directive. * `DebugElement.styles` affords access to element styles even in the absence of a real browser, thanks to the `DebugElement` abstraction. But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction. * Angular adds a directive to the injector of the element to which it is applied. The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance and its `defaultColor`. * `DebugElement.properties` affords access to the artificial custom property that is set by the directive --- ## Page: https://angular.dev/guide/testing/pipes A pipe class has one method, `transform`, that manipulates the input value into a transformed output value. The `transform` implementation rarely interacts with the DOM. Most pipes have no dependence on Angular other than the `@Pipe` metadata and an interface. Consider a `TitleCasePipe` that capitalizes the first letter of each word. Here's an implementation with a regular expression. import {Pipe, PipeTransform} from '@angular/core';@Pipe({name: 'titlecase', pure: true})/** Transform to Title Case: uppercase the first letter of the words in a string. */export class TitleCasePipe implements PipeTransform { transform(input: string): string { return input.length === 0 ? '' : input.replace(/\w\S*/g, (txt) => txt[0].toUpperCase() + txt.slice(1).toLowerCase()); }} Anything that uses a regular expression is worth testing thoroughly. Use simple Jasmine to explore the expected cases and the edge cases. import {TitleCasePipe} from './title-case.pipe';describe('TitleCasePipe', () => { // This pipe is a pure, stateless function so no need for BeforeEach const pipe = new TitleCasePipe(); it('transforms "abc" to "Abc"', () => { expect(pipe.transform('abc')).toBe('Abc'); }); it('transforms "abc def" to "Abc Def"', () => { expect(pipe.transform('abc def')).toBe('Abc Def'); }); // ... more tests ... it('leaves "Abc Def" unchanged', () => { expect(pipe.transform('Abc Def')).toBe('Abc Def'); }); it('transforms "abc-def" to "Abc-def"', () => { expect(pipe.transform('abc-def')).toBe('Abc-def'); }); it('transforms " abc def" to " Abc Def" (preserves spaces) ', () => { expect(pipe.transform(' abc def')).toBe(' Abc Def'); });}); These are tests of the pipe _in isolation_. They can't tell if the `TitleCasePipe` is working properly as applied in the application components. import {HttpClient, HttpHandler, provideHttpClient} from '@angular/common/http';import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing';import {fakeAsync, TestBed, tick} from '@angular/core/testing';import {provideRouter, Router} from '@angular/router';import {RouterTestingHarness} from '@angular/router/testing';import {asyncData, click} from '../../testing';import {Hero} from '../model/hero';import {sharedImports} from '../shared/shared';import {HeroDetailComponent} from './hero-detail.component';import {HeroDetailService} from './hero-detail.service';import {HeroListComponent} from './hero-list.component';////// Testing Vars //////let component: HeroDetailComponent;let harness: RouterTestingHarness;let page: Page;////// Tests //////describe('HeroDetailComponent', () => { describe('with HeroModule setup', heroModuleSetup); describe('when override its provided HeroDetailService', overrideSetup); describe('with FormsModule setup', formsModuleSetup); describe('with SharedModule setup', sharedModuleSetup);});///////////////////const testHero = getTestHeroes()[0];function overrideSetup() { class HeroDetailServiceSpy { testHero: Hero = {...testHero}; /* emit cloned test hero */ getHero = jasmine .createSpy('getHero') .and.callFake(() => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ saveHero = jasmine .createSpy('saveHero') .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes', component: HeroListComponent}, {path: 'heroes/:id', component: HeroDetailComponent}, ]), HttpClient, HttpHandler, // HeroDetailService at this level is IRRELEVANT! {provide: HeroDetailService, useValue: {}}, ], }), ) .overrideComponent(HeroDetailComponent, { set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}, }) .compileComponents(); }); let hdsSpy: HeroDetailServiceSpy; beforeEach(async () => { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetailComponent); page = new Page(); // get the component's injected HeroDetailServiceSpy hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any; harness.detectChanges(); }); it('should have called `getHero`', () => { expect(hdsSpy.getHero.calls.count()) .withContext('getHero called once') .toBe(1, 'getHero called once'); }); it("should display stub hero's name", () => { expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hdsSpy.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(new Event('input')); // tell Angular expect(component.hero.name).withContext('component hero has new name').toBe(newName); expect(hdsSpy.testHero.name).withContext('service hero unchanged before save').toBe(origName); click(page.saveBtn); expect(hdsSpy.saveHero.calls.count()).withContext('saveHero called once').toBe(1); tick(); // wait for async save to complete expect(hdsSpy.testHero.name).withContext('service hero has new name after save').toBe(newName); expect(TestBed.inject(Router).url).toEqual('/heroes'); }));}////////////////////import {getTestHeroes} from '../model/testing/test-hero.service';const firstHero = getTestHeroes()[0];function heroModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, HeroListComponent], providers: [ provideRouter([ {path: 'heroes/:id', component: HeroDetailComponent}, {path: 'heroes', component: HeroListComponent}, ]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach(async () => { expectedHero = firstHero; await createComponent(expectedHero.id); }); it("should display that hero's name", () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'})); expect(TestBed.inject(Router).url).toEqual('/heroes/41'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(TestBed.inject(Router).url).toEqual('/heroes/41'); })); it('should convert hero name to Title Case', async () => { harness.fixture.autoDetectChanges(); // get the name's input and display elements from the DOM const hostElement: HTMLElement = harness.routeNativeElement!; const nameInput: HTMLInputElement = hostElement.querySelector('input')!; const nameDisplay: HTMLElement = hostElement.querySelector('span')!; // simulate user entering a new name into the input box nameInput.value = 'quick BROWN fOx'; // Dispatch a DOM event so that Angular learns of input value change. nameInput.dispatchEvent(new Event('input')); // Wait for Angular to update the display binding through the title pipe await harness.fixture.whenStable(); expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); }); describe('when navigate to non-existent hero id', () => { beforeEach(async () => { await createComponent(999); }); it('should try to navigate back to hero list', () => { expect(TestBed.inject(Router).url).toEqual('/heroes'); }); });}/////////////////////import {FormsModule} from '@angular/forms';import {TitleCasePipe} from '../shared/title-case.pipe';import {appConfig} from '../app.config';function formsModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [FormsModule, HeroDetailComponent, TitleCasePipe], providers: [ provideHttpClient(), provideHttpClientTesting(), provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}///////////////////////function sharedModuleSetup() { beforeEach(async () => { await TestBed.configureTestingModule( Object.assign({}, appConfig, { imports: [HeroDetailComponent, sharedImports], providers: [ provideRouter([{path: 'heroes/:id', component: HeroDetailComponent}]), provideHttpClient(), provideHttpClientTesting(), ], }), ).compileComponents(); }); it("should display 1st hero's name", async () => { const expectedHero = firstHero; await createComponent(expectedHero.id).then(() => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });}/////////// Helpers //////** Create the HeroDetailComponent, initialize it, set test variables */async function createComponent(id: number) { harness = await RouterTestingHarness.create(); component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetailComponent); page = new Page(); const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`); const hero = getTestHeroes().find((h) => h.id === Number(id)); request.flush(hero ? [hero] : []); harness.detectChanges();}class Page { // getter properties wait to query the DOM until called. get buttons() { return this.queryAll<HTMLButtonElement>('button'); } get saveBtn() { return this.buttons[0]; } get cancelBtn() { return this.buttons[1]; } get nameDisplay() { return this.query<HTMLElement>('span'); } get nameInput() { return this.query<HTMLInputElement>('input'); } //// query helpers //// private query<T>(selector: string): T { return harness.routeNativeElement!.querySelector(selector)! as T; } private queryAll<T>(selector: string): T[] { return harness.routeNativeElement!.querySelectorAll(selector) as any as T[]; }} --- ## Page: https://angular.dev/guide/testing/debugging 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](https://angular.dev/guide/testing#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.  --- ## Page: https://angular.dev/guide/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`](https://angular.dev/#testbed-api-summary) and [`ComponentFixture`](https://angular.dev/#component-fixture-api-summary) classes are covered separately. 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. A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods. These are rarely needed. 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. Here are the most important properties for testers, in order of likely utility. 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. 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: 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": import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // .compileComponents(); // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); }); describe('lifecycle hooks w/ MyIfParentComp', () => { let fixture: ComponentFixture<MyIfParentComponent>; let parent: MyIfParentComponent; let child: MyIfChildComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule, MyIfChildComponent, MyIfParentComponent], }); fixture = TestBed.createComponent(MyIfParentComponent); parent = fixture.componentInstance; }); it('should instantiate parent component', () => { expect(parent).withContext('parent component should exist').not.toBeNull(); }); it('parent component OnInit should NOT be called before first detectChanges()', () => { expect(parent.ngOnInitCalled).toBe(false); }); it('parent component OnInit should be called after first detectChanges()', () => { fixture.detectChanges(); expect(parent.ngOnInitCalled).toBe(true); }); it('child component should exist after OnInit', () => { fixture.detectChanges(); getChild(); expect(child instanceof MyIfChildComponent) .withContext('should create child') .toBe(true); }); it("should have called child component's OnInit ", () => { fixture.detectChanges(); getChild(); expect(child.ngOnInitCalled).toBe(true); }); it('child component called OnChanges once', () => { fixture.detectChanges(); getChild(); expect(child.ngOnChangesCounter).toBe(1); }); it('changed parent value flows to child', () => { fixture.detectChanges(); getChild(); parent.parentValue = 'foo'; fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo'); }); // must be async test to see child flow to parent it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); child.childValue = 'bar'; return new Promise<void>((resolve) => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }).then(() => { fixture.detectChanges(); expect(child.ngOnChangesCounter) .withContext('expected 2 changes: initial value and changed value') .toBe(2); expect(parent.parentValue) .withContext('parentValue should eq changed parent value') .toBe('bar'); }); })); it('clicking "Close Child" triggers child OnDestroy', () => { fixture.detectChanges(); getChild(); const btn = fixture.debugElement.query(By.css('button')); click(btn); fixture.detectChanges(); expect(child.ngOnDestroyCalled).toBe(true); }); ////// helpers /// /** * Get the MyIfChildComp from parent; fail w/ good message if cannot. */ function getChild() { let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp // The Hard Way: requires detailed knowledge of the parent template try { childDe = fixture.debugElement.children[4].children[0]; } catch (err) { /* we'll report the error */ } // DebugElement.queryAll: if we wanted all of many instances: childDe = fixture.debugElement.queryAll( (de) => de.componentInstance instanceof MyIfChildComponent, )[0]; // WE'LL USE THIS APPROACH ! // DebugElement.query: find first instance (if any) childDe = fixture.debugElement.query( (de) => de.componentInstance instanceof MyIfChildComponent, ); if (childDe && childDe.componentInstance) { child = childDe.componentInstance; } else { fail('Unable to find MyIfChildComp within MyIfParentComp'); } return child; } });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';} import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {DebugElement} from '@angular/core';import {Router} from '@angular/router';import {addMatchers} from '../../testing';import {HeroService} from '../model/hero.service';import {getTestHeroes, TestHeroService} from '../model/testing/test-hero.service';import {HeroListComponent} from './hero-list.component';import {HighlightDirective} from '../shared/highlight.directive';import {appConfig} from '../app.config';const HEROES = getTestHeroes();let comp: HeroListComponent;let fixture: ComponentFixture<HeroListComponent>;let page: Page;/////// Tests //////describe('HeroListComponent', () => { beforeEach(waitForAsync(() => { addMatchers(); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); TestBed.configureTestingModule( Object.assign({}, appConfig, { providers: [ {provide: HeroService, useClass: TestHeroService}, {provide: Router, useValue: routerSpy}, ], }), ) .compileComponents() .then(createComponent); })); it('should display heroes', () => { expect(page.heroRows.length).toBeGreaterThan(0); }); it('1st hero should match 1st test hero', () => { const expectedHero = HEROES[0]; const actualHero = page.heroRows[0].textContent; expect(actualHero).withContext('hero.id').toContain(expectedHero.id.toString()); expect(actualHero).withContext('hero.name').toContain(expectedHero.name); }); it('should select hero on click', fakeAsync(() => { const expectedHero = HEROES[1]; const btn = page.heroRows[1].querySelector('button'); btn!.dispatchEvent(new Event('click')); tick(); // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService expect(comp.selectedHero).toEqual(expectedHero); })); it('should navigate to selected hero detail on click', fakeAsync(() => { const expectedHero = HEROES[1]; const btn = page.heroRows[1].querySelector('button'); btn!.dispatchEvent(new Event('click')); tick(); // should have navigated expect(page.navSpy.calls.any()).withContext('navigate called').toBe(true); // composed hero detail will be URL like 'heroes/42' // expect link array with the route path and hero id // first argument to router.navigate is link array const navArgs = page.navSpy.calls.first().args[0]; expect(navArgs[0]).withContext('nav to heroes detail URL').toContain('heroes'); expect(navArgs[1]).withContext('expected hero.id').toBe(expectedHero.id); })); it('should find `HighlightDirective` with `By.directive', () => { // Can find DebugElement either by css selector or by directive const h2 = fixture.debugElement.query(By.css('h2')); const directive = fixture.debugElement.query(By.directive(HighlightDirective)); expect(h2).toBe(directive); }); it('should color header with `HighlightDirective`', () => { const h2 = page.highlightDe.nativeElement as HTMLElement; const bgColor = h2.style.backgroundColor; // different browsers report color values differently const isExpectedColor = bgColor === 'gold' || bgColor === 'rgb(255, 215, 0)'; expect(isExpectedColor).withContext('backgroundColor').toBe(true); }); it("the `HighlightDirective` is among the element's providers", () => { expect(page.highlightDe.providerTokens) .withContext('HighlightDirective') .toContain(HighlightDirective); });});/////////// Helpers //////** Create the component and set the `page` test variables */function createComponent() { fixture = TestBed.createComponent(HeroListComponent); comp = fixture.componentInstance; // change detection triggers ngOnInit which gets a hero fixture.detectChanges(); return fixture.whenStable().then(() => { // got the heroes and updated component // change detection updates the view fixture.detectChanges(); page = new Page(); });}class Page { /** Hero line elements */ heroRows: HTMLLIElement[]; /** Highlighted DebugElement */ highlightDe: DebugElement; /** Spy on router navigate method */ navSpy: jasmine.Spy; constructor() { const heroRowNodes = fixture.nativeElement.querySelectorAll('li'); this.heroRows = Array.from(heroRowNodes); // Find the first element with an attached HighlightDirective this.highlightDe = fixture.debugElement.query(By.directive(HighlightDirective)); // Get the component's injected router navigation spy const routerSpy = fixture.debugElement.injector.get(Router); this.navSpy = routerSpy.navigate as jasmine.Spy; }} --- ## Page: https://angular.dev/guide/testing/component-harnesses-overview A **component harness** is a class that allows tests to interact with components the way an end user does via a supported API. You can create test harnesses for any component, ranging from small reusable widgets to full pages. Harnesses offer several benefits: * They make tests less brittle by insulating themselves against implementation details of a component, such as its DOM structure * They make tests become more readable and easier to maintain * They can be used across multiple testing environments // Example of test with a harness for a component called MyButtonComponentit('should load button with exact text', async () => { const button = await loader.getHarness(MyButtonComponentHarness); expect(await button.getText()).toBe('Confirm');}); Component harnesses are especially useful for shared UI widgets. Developers often write tests that depend on private implementation details of widgets, such as DOM structure and CSS classes. Those dependencies make tests brittle and hard to maintain. Harnesses offer an alternative— a supported API that interacts with the widget the same way an end-user does. Widget implementation changes now become less likely to break user tests. For example, [Angular Material](https://material.angular.io/components/categories) provides a test harness for each component in the library. Component harnesses support multiple testing environments. You can use the same harness implementation in both unit and end-to-end tests. Test authors only need to learn one API and component authors don't have to maintain separate unit and end-to-end test implementations. Many developers can be categorized by one of the following developer type categories: test authors, component harness authors, and harness environment authors. Use the table below to find the most relevant section in this guide based on these categories: | Developer Type | Description | Relevant Section | | --- | --- | --- | | Test Authors | Developers that use component harnesses written by someone else to test their application. For example, this could be an app developer who uses a third-party menu component and needs to interact with the menu in a unit test. | [Using component harnesses in tests](https://angular.dev/guide/testing/using-component-harnesses) | | Component harness authors | Developers who maintain some reusable Angular components and want to create a test harness for its users to use in their tests. For example, an author of a third party Angular component library or a developer who maintains a set of common components for a large Angular application. | [Creating component harnesses for your components](https://angular.dev/guide/testing/creating-component-harnesses) | | Harness environment authors | Developers who want to add support for using component harnesses in additional testing environments. For information on supported testing environments out-of-the-box, see the [test harness environments and loaders](https://angular.dev/guide/testing/using-component-harnesses#test-harness-environments-and-loaders). | [Adding support for additional testing environments](https://angular.dev/guide/testing/component-harnesses-testing-environments) | For the full API reference, please see the [Angular CDK's component harness API reference page](https://material.angular.io/cdk/testing/api). --- ## Page: https://angular.dev/guide/testing/using-component-harnesses ## [Before you start](https://angular.dev/#before-you-start) **TIP:** This guide assumes you've already read the [component harnesses overview guide](https://angular.dev/guide/testing/component-harnesses-overview). Read that first if you're new to using component harnesses. ### [CDK Installation](https://angular.dev/#cdk-installation) The [Component Dev Kit (CDK)](https://material.angular.io/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI: ng add @angular/cdk ## [Test harness environments and loaders](https://angular.dev/#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](https://developer.mozilla.org/en-US/docs/Web/WebDriver) Each environment provides a **harness loader**. The loader creates the harness instances you use throughout your tests. See below for more specific guidance on supported testing environments. Additional testing environments require custom bindings. See the [adding harness support for additional testing environments guide](https://angular.dev/guide/testing/component-harnesses-testing-environments) for more information. ### [Using the loader from `TestbedHarnessEnvironment` for unit tests](https://angular.dev/#using-the-loader-from-testbedharnessenvironment-for-unit-tests) For unit tests you can create a harness loader from [TestbedHarnessEnvironment](https://material.angular.io/cdk/testing/api#TestbedHarnessEnvironment). This environment uses a [component fixture](https://angular.dev/api/core/testing/ComponentFixture) 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 fixtureconst loader = TestbedHarnessEnvironment.loader(fixture);...// Use the loader to get harness instancesconst 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](https://angular.dev/#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](https://angular.dev/#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 elementconst myComponentHarness = await loader.getHarness(MyComponent);// Get harnesses for all instances of the elementconst myComponentHarnesses = await loader.getHarnesses(MyComponent); 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](https://angular.dev/#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](https://angular.dev/#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' selectorconst childLoader = await myComponentHarness.getLoader('.child');// Get loaders for all instances of child elements with '.child' selectorconst allChildLoaders = await myComponentHarness.getAllChildLoaders('.child'); ### [Filtering harnesses](https://angular.dev/#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 predicateconst 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 selectorconst 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](https://angular.dev/#using-test-harness-apis) While every harness defines an API specific to its corresponding component, they all share a common base class, [ComponentHarness](https://material.angular.io/cdk/testing/api#ComponentHarness). This base class defines a static property, `hostSelector`, that matches the harness class to instances of the component in the DOM. Beyond that, the API of any given harness is specific to its corresponding component; refer to the component's documentation to learn how to use a specific harness. As an example, the following is a test for a component that uses the [Angular Material slider component harness](https://material.angular.io/components/slider/api#MatSliderHarness): it('should get value of slider thumb', async () => { const slider = await loader.getHarness(MatSliderHarness); const thumb = await slider.getEndThumb(); expect(await thumb.getValue()).toBe(50);}); ## [Interop with Angular change detection](https://angular.dev/#interop-with-angular-change-detection) By default, test harnesses runs Angular's [change detection](https://angular.dev/best-practices/runtime-performance) before reading the state of a DOM element and after interacting with a DOM element. There may be times that you need finer-grained control over change detection in your tests. such as checking the state of a component while an async operation is pending. In these cases use the `manualChangeDetection` function to disable automatic handling of change detection for a block of code. it('checks state while async action is in progress', async () => { const buttonHarness = loader.getHarness(MyButtonHarness); await manualChangeDetection(async () => { await buttonHarness.click(); fixture.detectChanges(); // Check expectations while async click operation is in progress. expect(isProgressSpinnerVisible()).toBe(true); await fixture.whenStable(); // Check expectations after async click operation complete. expect(isProgressSpinnerVisible()).toBe(false); });}); Almost all harness methods are asynchronous and return a `Promise` to support the following: * Support for unit tests * Support for end-to-end tests * Insulate tests against changes in asynchronous behavior The Angular team recommends using [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) to improve the test readability. Calling `await` blocks the execution of your test until the associated `Promise` resolves. Occasionally, you may want to perform multiple actions simultaneously and wait until they're all done rather than performing each action sequentially. For example, read multiple properties of a single component. In these situations use the `parallel` 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);}); --- ## Page: https://angular.dev/guide/testing/creating-component-harnesses ## [Before you start](https://angular.dev/#before-you-start) **TIP:** This guide assumes you've already read the [component harnesses overview guide](https://angular.dev/guide/testing/component-harnesses-overview). Read that first if you're new to using component harnesses. ### [When does creating a test harness make sense?](https://angular.dev/#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](https://angular.dev/#cdk-installation) The [Component Dev Kit (CDK)](https://material.angular.io/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI: ng add @angular/cdk ## [Extending `ComponentHarness`](https://angular.dev/#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](https://angular.dev/guide/testing/using-component-harnesses#filtering-harnesses) covers this in more detail. ## [Finding elements in the component's DOM](https://angular.dev/#finding-elements-in-the-components-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 `ngIf` 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](https://material.angular.io/cdk/testing/api#ComponentHarness) for the full list details of the different `locatorFor` methods. For example, the `MyPopupHarness` example discussed above could provide methods to get the trigger and content elements as follows: class MyPopupHarness extends ComponentHarness { static hostSelector = 'my-popup'; /** Gets the trigger element */ getTriggerElement = this.locatorFor('button'); /** Gets the content element. */ getContentElement = this.locatorForOptional('.my-popup-content');} ## [Working with `TestElement` instances](https://angular.dev/#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](https://material.angular.io/cdk/testing/api#TestElement) 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](https://angular.dev/#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](https://material.angular.io/cdk/testing/api#ComponentHarness) for the full list of the different locatorFor methods. For example, consider a menu build using the popup from above: @Directive({ selector: 'my-menu-item'})class MyMenuItem {}@Component({ selector: 'my-menu', template: ` <my-popup> <ng-content></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`](https://angular.dev/#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](https://material.angular.io/cdk/testing/api#HarnessPredicate) for the full description. For example, when working with a menu it is useful to filter based on trigger text and to filter menu items based on their text: interface MyMenuHarnessFilters extends BaseHarnessFilters { /** Filters based on the trigger text for the menu. */ triggerText?: string | RegExp;}interface MyMenuItemHarnessFilters extends BaseHarnessFilters { /** Filters based on the text of the menu item. */ text?: string | RegExp;}class MyMenuHarness extends ComponentHarness { static hostSelector = 'my-menu'; /** Creates a `HarnessPredicate` used to locate a particular `MyMenuHarness`. */ static with(options: MyMenuHarnessFilters): HarnessPredicate<MyMenuHarness> { return new HarnessPredicate(MyMenuHarness, options) .addOption('trigger text', options.triggerText, (harness, text) => HarnessPredicate.stringMatches(harness.getTriggerText(), text)); } protected getPopupHarness = this.locatorFor(MyPopupHarness); /** Gets the text of the menu trigger. */ async getTriggerText(): Promise<string> { const popupHarness = await this.getPopupHarness(); return popupHarness.getTriggerText(); } ...}class MyMenuItemHarness extends ComponentHarness { static hostSelector = 'my-menu-item'; /** Creates a `HarnessPredicate` used to locate a particular `MyMenuItemHarness`. */ static with(options: MyMenuItemHarnessFilters): HarnessPredicate<MyMenuItemHarness> { return new HarnessPredicate(MyMenuItemHarness, options) .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text)); } /** Gets the text of the menu item. */ async getText(): Promise<string> { const host = await this.host(); return host.text(); }} You can pass a `HarnessPredicate` 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](https://angular.dev/#creating-harnessloader-for-elements-that-use-content-projection) Some components project additional content into the component's template. See the [content projection guide](https://angular.dev/guide/components/content-projection) 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](https://material.angular.io/cdk/testing/api#HarnessLoader) 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](https://angular.dev/#accessing-elements-outside-of-the-components-host-element) There are times when a component harness might need to access elements outside of its corresponding component's host element. For example, code that displays a floating element or pop-up often attaches DOM elements directly to the document body, such as the `Overlay` service in Angular CDK. In this case, `ComponentHarness` 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](https://angular.dev/#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. --- ## Page: https://angular.dev/guide/testing/component-harnesses-testing-environments ## [Before you start](https://angular.dev/#before-you-start) **TIP:** This guide assumes you've already read the [component harnesses overview guide](https://angular.dev/guide/testing/component-harnesses-overview). Read that first if you're new to using component harnesses. ### [When does adding support for a test environment make sense?](https://angular.dev/#when-does-adding-support-for-a-test-environment-make-sense) To use component harnesses in the following environments, you can use Angular CDK's two built-in environments: * Unit tests * WebDriver end-to-end tests To use a supported testing environment, read the [Creating harnesses for your components guide](https://angular.dev/guide/testing/creating-component-harnesses). Otherwise, to add support for other environments, you need to define how to interact with a DOM element and how DOM interactions work in your environment. Continue reading to learn more. ### [CDK Installation](https://angular.dev/#cdk-installation) The [Component Dev Kit (CDK)](https://material.angular.io/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI: ng add @angular/cdk ## [Creating a `TestElement` implementation](https://angular.dev/#creating-a-testelement-implementation) Every test environment must define a `TestElement` implementation. The `TestElement` interface serves as an environment-agnostic representation of a DOM element. It enables harnesses to interact with DOM elements regardless of the underlying environment. Because some environments don't support interacting with DOM elements synchronously (e.g. WebDriver), all `TestElement` methods are asynchronous, returning a `Promise` with the result of the operation. `TestElement` offers a number of methods to interact with the underlying DOM such as `blur()`, `click()`, `getAttribute()`, and more. See the [TestElement API reference page](https://material.angular.io/cdk/testing/api#TestElement) for the full list of methods. The `TestElement` interface consists largely of methods that resemble methods available on `HTMLElement`. Similar methods exist in most test environments, which makes implementing the methods fairly straightforward. However, one important difference to note when implementing the `sendKeys` method, is that the key codes in the `TestKey` enum likely differ from the key codes used in the test environment. Environment authors should maintain a mapping from `TestKey` codes to the codes used in the particular testing environment. The [UnitTestElement](https://github.com/angular/components/blob/main/src/cdk/testing/testbed/unit-test-element.ts#L33) and [SeleniumWebDriverElement](https://github.com/angular/components/blob/main/src/cdk/testing/selenium-webdriver/selenium-webdriver-keys.ts#L16) implementations in Angular CDK serve as good examples of implementations of this interface. ## [Creating a `HarnessEnvironment` implementation](https://angular.dev/#creating-a-harnessenvironment-implementation) Test authors use `HarnessEnvironment` to create component harness instances for use in tests. `HarnessEnvironment` is an abstract class that must be extended to create a concrete subclass for the new environment. When supporting a new test environment, create a `HarnessEnvironment` subclass that adds concrete implementations for all abstract members. `HarnessEnvironment` has a generic type parameter: `HarnessEnvironment<E>`. This parameter, `E`, represents the raw element type of the environment. For example, this parameter is Element for unit test environments. The following are the abstract methods that must be implemented: | Method | Description | | --- | --- | | `abstract getDocumentRoot(): E` | Gets the root element for the environment (e.g. `document.body`). | | `abstract createTestElement(element: E): TestElement` | Creates a `TestElement` for the given raw element. | | `abstract createEnvironment(element: E): HarnessEnvironment` | Creates a `HarnessEnvironment` rooted at the given raw element. | | `abstract getAllRawElements(selector: string): Promise<E[]>` | Gets all of the raw elements under the root element of the environment matching the given selector. | | `abstract forceStabilize(): Promise<void>` | Gets a `Promise` that resolves when the `NgZone` is stable. Additionally, if applicable, tells `NgZone` to stabilize (e.g. calling `flush()` in a `fakeAsync` test). | | `abstract waitForTasksOutsideAngular(): Promise<void>` | Gets a `Promise` that resolves when the parent zone of `NgZone` is stable. | In addition to implementing the missing methods, this class should provide a way for test authors to get `ComponentHarness` instances. You should define a protected constructor and provide a static method called `loader` that returns a `HarnessLoader` instance. This allows test authors to write code like: `SomeHarnessEnvironment.loader().getHarness(...)`. Depending on the needs of the particular environment, the class may provide several different static methods or require arguments to be passed. (e.g. the `loader` method on `TestbedHarnessEnvironment` takes a `ComponentFixture`, and the class provides additional static methods called `documentRootLoader` and `harnessForFixture`). The [`TestbedHarnessEnvironment`](https://github.com/angular/components/blob/main/src/cdk/testing/testbed/testbed-harness-environment.ts#L89) and [SeleniumWebDriverHarnessEnvironment](https://github.com/angular/components/blob/main/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts#L71) implementations in Angular CDK serve as good examples of implementations of this interface. ## [Handling auto change detection](https://angular.dev/#handling-auto-change-detection) In order to support the `manualChangeDetection` and parallel APIs, your environment should install a handler for the auto change detection status. When your environment wants to start handling the auto change detection status it can call `handleAutoChangeDetectionStatus(handler)`. The handler function will receive a `AutoChangeDetectionStatus` which has two properties `isDisabled` and `onDetectChangesNow()`. See the [AutoChangeDetectionStatus API reference page](https://material.angular.io/cdk/testing/api#AutoChangeDetectionStatus) for more information. If your environment wants to stop handling auto change detection status it can call `stopHandlingAutoChangeDetectionStatus()`. --- ## Page: https://angular.dev/guide/testing/guide/testing#configuration ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/di ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/ngmodules ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/http/testing ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/api/core/testing/waitForAsync ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/components-scenarios#waitForAsync ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/api/core/testing/ComponentFixture ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/components/lifecycle ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/api/core/testing/tick ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/ngmodules/feature-modules ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/utility-apis#metadata-override-object ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/templates/pipes ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/using-component-harnesses ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/creating-component-harnesses ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/component-harnesses-testing-environments ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/testing/component-harnesses-overview ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/testing/guide/components/content-projection ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/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](https://angular.dev/#learn-about-angular-internationalization) --- ## Page: https://angular.dev/guide/i18n/add-package To take advantage of the localization features of Angular, use the [Angular CLI](https://angular.dev/cli "CLI") to add the `@angular/localize` package to your project. To add the `@angular/localize` package, use the following command to update the `package.json` and TypeScript configuration files in your project. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr It adds `types: ["@angular/localize"]` in the TypeScript configuration files. It also adds line `/// <reference types="@angular/localize" />` at the top of the `main.ts` file which is the reference to the type definition. If `@angular/localize` is not installed and you try to build a localized version of your project (for example, while using the `i18n` attributes in templates), the [Angular CLI](https://angular.dev/cli "CLI") will generate an error, which would contain the steps that you can take to enable i18n for your project. ## [Options](https://angular.dev/#options) | OPTION | DESCRIPTION | VALUE TYPE | DEFAULT VALUE | | --- | --- | --- | --- | | `--project` | The name of the project. | `string` | | | `--use-at-runtime` | If set, then `$localize` can be used at runtime. Also `@angular/localize` gets included in the `dependencies` section of `package.json`, rather than `devDependencies`, which is the default. | `boolean` | `false` | For more available options, see `ng add` in [Angular CLI](https://angular.dev/cli "CLI"). ## [What's next](https://angular.dev/#whats-next) [Refer to locales by ID](https://angular.dev/guide/i18n/locale-id) --- ## Page: https://angular.dev/guide/i18n/locale-id Angular uses the Unicode _locale identifier_ (Unicode locale ID) to find the correct locale data for internationalization of text strings. ### Unicode locale ID * A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification](https://cldr.unicode.org/index/cldr-spec "Core"). For more information about locale IDs, see \[Unicode Language and Locale Identifiers\]\[UnicodeCldrDevelopmentCoreSpecificationLocaleIDs\]. * CLDR and Angular use [BCP 47 tags](https://www.rfc-editor.org/info/bcp47 "BCP") as the base for the locale ID A locale ID specifies the language, country, and an optional code for further variants or subdivisions. A locale ID consists of the language identifier, a hyphen (`-`) character, and the locale extension. {language_id}-{locale_extension} **HELPFUL:** To accurately translate your Angular project, you must decide which languages and locales you are targeting for internationalization. Many countries share the same language, but differ in usage. The differences include grammar, punctuation, formats for currency, decimal numbers, dates, and so on. For the examples in this guide, use the following languages and locales. | Language | Locale | Unicode locale ID | | --- | --- | --- | | English | Canada | `en-CA` | | English | United States of America | `en-US` | | French | Canada | `fr-CA` | | French | France | `fr-FR` | The [Angular repository](https://github.com/angular/angular/tree/main/packages/common/locales "angular/packages/common/locales") includes common locales. ## [Set the source locale ID](https://angular.dev/#set-the-source-locale-id) Use the Angular CLI to set the source language in which you are writing the component template and code. By default, Angular uses `en-US` as the source locale of your project. To change the source locale of your project for the build, complete the following actions. 1. Open the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file. 2. Add or modify the `sourceLocale` field inside the `i18n` section: { "projects": { "your-project": { "i18n": { "sourceLocale": "ca" // Use your desired locale code } } }} ## [What's next](https://angular.dev/#whats-next) [Format data based on locale](https://angular.dev/guide/i18n/format-data-locale) --- ## Page: https://angular.dev/guide/i18n/format-data-locale Angular provides the following built-in data transformation [pipes](https://angular.dev/guide/templates/pipes). The data transformation pipes use the [`LOCALE_ID`](https://angular.dev/api/core/LOCALE_ID "LOCALE_ID") token to format data based on rules of each locale. | Data transformation pipe | Details | | --- | --- | | [`DatePipe`](https://angular.dev/api/common/DatePipe "DatePipe") | Formats a date value. | | [`CurrencyPipe`](https://angular.dev/api/common/CurrencyPipe "CurrencyPipe") | Transforms a number into a currency string. | | [`DecimalPipe`](https://angular.dev/api/common/DecimalPipe "DecimalPipe") | Transforms a number into a decimal number string. | | [`PercentPipe`](https://angular.dev/api/common/PercentPipe "PercentPipe") | Transforms a number into a percentage string. | ## [Use DatePipe to display the current date](https://angular.dev/#use-datepipe-to-display-the-current-date) To display the current date in the format for the current locale, use the following format for the `DatePipe`. {{ today | date }} ## [Override current locale for CurrencyPipe](https://angular.dev/#override-current-locale-for-currencypipe) Add the `locale` parameter to the pipe to override the current value of `LOCALE_ID` token. To force the currency to use American English (`en-US`), use the following format for the `CurrencyPipe` {{ amount | currency : 'en-US' }} **HELPFUL:** The locale specified for the `CurrencyPipe` overrides the global `LOCALE_ID` token of your application. ## [What's next](https://angular.dev/#whats-next) [Prepare component for translation](https://angular.dev/guide/i18n/prepare) --- ## Page: https://angular.dev/guide/i18n/prepare To prepare your project for translation, complete the following actions. * Use the `i18n` attribute to mark text in component templates * Use the `i18n-` attribute to mark attribute text strings in component templates * Use the `$localize` tagged message string to mark text strings in component code ## [Mark text in component template](https://angular.dev/#mark-text-in-component-template) In a component template, the i18n metadata is the value of the `i18n` attribute. <element i18n="{i18n_metadata}">{string_to_translate}</element> Use the `i18n` attribute to mark a static text message in your component templates for translation. Place it on every element tag that contains fixed text you want to translate. **HELPFUL:** The `i18n` attribute is a custom attribute that the Angular tools and compilers recognize. ### [`i18n` example](https://angular.dev/#i18n-example) The following `<h1>` tag displays a simple English language greeting, "Hello i18n!". <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> To mark the greeting for translation, add the `i18n` attribute to the `<h1>` tag. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> ### [using conditional statement with `i18n`](https://angular.dev/#using-conditional-statement-with-i18n) The following `<div>` tag will display translated text as part of `div` and `aria-label` based on toggle status <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> import {Component, computed, signal} from '@angular/core';import {$localize} from '@angular/localize/init';@Component({ selector: 'app-root', templateUrl: './app.component.html',})export class AppComponent { minutes = 0; gender = 'female'; fly = true; logo = '${this.baseUrl}/angular.svg'; toggle = signal(false); toggleAriaLabel = computed(() => { return this.toggle() ? $localize`:Toggle Button|A button to toggle status:Show` : $localize`:Toggle Button|A button to toggle status:Hide`; }); inc(i: number) { this.minutes = Math.min(5, Math.max(0, this.minutes + i)); } male() { this.gender = 'male'; } female() { this.gender = 'female'; } other() { this.gender = 'other'; } toggleDisplay() { this.toggle.update((toggle) => !toggle); }} ### [Translate inline text without HTML element](https://angular.dev/#translate-inline-text-without-html-element) Use the `<ng-container>` element to associate a translation behavior for specific text without changing the way text is displayed. **HELPFUL:** Each HTML element creates a new DOM element. To avoid creating a new DOM element, wrap the text in an `<ng-container>` element. The following example shows the `<ng-container>` element transformed into a non-displayed HTML comment. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> ## [Mark element attributes for translations](https://angular.dev/#mark-element-attributes-for-translations) In a component template, the i18n metadata is the value of the `i18n-{attribute_name}` attribute. <element i18n-{attribute_name}="{i18n_metadata}" {attribute_name}="{attribute_value}" /> The attributes of HTML elements include text that should be translated along with the rest of the displayed text in the component template. Use `i18n-{attribute_name}` with any attribute of any element and replace `{attribute_name}` with the name of the attribute. Use the following syntax to assign a meaning, description, and custom ID. i18n-{attribute_name}="{meaning}|{description}@@{id}" ### [`i18n-title` example](https://angular.dev/#i18n-title-example) To translate the title of an image, review this example. The following example displays an image with a `title` attribute. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> To mark the title attribute for translation, complete the following action. 1. Add the `i18n-title` attribute The following example displays how to mark the `title` attribute on the `img` tag by adding `i18n-title`. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> ## [Mark text in component code](https://angular.dev/#mark-text-in-component-code) In component code, the translation source text and the metadata are surrounded by backtick (`` ` ``) characters. Use the [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string to mark a string in your code for translation. $localize`string_to_translate`; The i18n metadata is surrounded by colon (`:`) characters and prepends the translation source text. $localize`:{i18n_metadata}:string_to_translate` ### [Include interpolated text](https://angular.dev/#include-interpolated-text) Include [interpolations](https://angular.dev/guide/templates/binding#render-dynamic-text-with-text-interpolation) in a [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string. $localize`string_to_translate ${variable_name}`; ### [Name the interpolation placeholder](https://angular.dev/#name-the-interpolation-placeholder) $localize`string_to_translate ${variable_name}:placeholder_name:`; ### [Conditional syntax for translations](https://angular.dev/#conditional-syntax-for-translations) return this.show ? $localize`Show Tabs` : $localize`Hide tabs`; {meaning}|{description}@@{custom_id} The following parameters provide context and additional information to reduce confusion for your translator. | Metadata parameter | Details | | --- | --- | | Custom ID | Provide a custom identifier | | Description | Provide additional information or context | | Meaning | Provide the meaning or intent of the text within the specific context | For additional information about custom IDs, see [Manage marked text with custom IDs](https://angular.dev/guide/i18n/manage-marked-text "Manage"). ### [Add helpful descriptions and meanings](https://angular.dev/#add-helpful-descriptions-and-meanings) To translate a text message accurately, provide additional information or context for the translator. Add a _description_ of the text message as the value of the `i18n` attribute or [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string. The following example shows the value of the `i18n` attribute. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The following example shows the value of the [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string with a description. $localize`:An introduction header for this sample:Hello i18n!`; The translator may also need to know the meaning or intent of the text message within this particular application context, in order to translate it the same way as other text with the same meaning. Start the `i18n` attribute value with the _meaning_ and separate it from the _description_ with the `|` character: `{meaning}|{description}`. #### [`h1` example](https://angular.dev/#h1-example) For example, you may want to specify that the `<h1>` tag is a site header that you need translated the same way, whether it is used as a header or referenced in another section of text. The following example shows how to specify that the `<h1>` tag must be translated as a header or referenced elsewhere. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The result is any text marked with `site header`, as the _meaning_ is translated exactly the same way. The following code example shows the value of the [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string with a meaning and a description. $localize`:site header|An introduction header for this sample:Hello i18n!`; ### How meanings control text extraction and merges The Angular extraction tool generates a translation unit entry for each `i18n` attribute in a template. The Angular extraction tool assigns each translation unit a unique ID based on the _meaning_ and _description_. The same text elements with different _meanings_ are extracted with different IDs. For example, if the word "right" uses the following two definitions in two different locations, the word is translated differently and merged back into the application as different translation entries. * `correct` as in "you are right" * `direction` as in "turn right" If the same text elements meet the following conditions, the text elements are extracted only once and use the same ID. * Same meaning or definition * Different descriptions That one translation entry is merged back into the application wherever the same text elements appear. ## [ICU expressions](https://angular.dev/#icu-expressions) ICU expressions help you mark alternate text in component templates to meet conditions. An ICU expression includes a component property, an ICU clause, and the case statements surrounded by open curly brace (`{`) and close curly brace (`}`) characters. { component_property, icu_clause, case_statements } The component property defines the variable An ICU clause defines the type of conditional text. | ICU clause | Details | | --- | --- | | [`plural`](https://angular.dev/guide/i18n/prepare#mark-plurals "Mark") | Mark the use of plural numbers | | [`select`](https://angular.dev/guide/i18n/prepare#mark-alternates-and-nested-expressions "Mark") | Mark choices for alternate text based on your defined string values | To simplify translation, use International Components for Unicode clauses (ICU clauses) with regular expressions. ### [Mark plurals](https://angular.dev/#mark-plurals) Different languages have different pluralization rules that increase the difficulty of translation. Because other locales express cardinality differently, you may need to set pluralization categories that do not align with English. Use the `plural` clause to mark expressions that may not be meaningful if translated word-for-word. { component_property, plural, pluralization_categories } After the pluralization category, enter the default text (English) surrounded by open curly brace (`{`) and close curly brace (`}`) characters. pluralization_category { } The following pluralization categories are available for English and may change based on the locale. | Pluralization category | Details | Example | | --- | --- | --- | | `zero` | Quantity is zero | `=0 { }` `zero { }` | | `one` | Quantity is 1 | `=1 { }` `one { }` | | `two` | Quantity is 2 | `=2 { }` `two { }` | | `few` | Quantity is 2 or more | `few { }` | | `many` | Quantity is a large number | `many { }` | | `other` | The default quantity | `other { }` | If none of the pluralization categories match, Angular uses `other` to match the standard fallback for a missing category. other { default_quantity } Many locales don't support some of the pluralization categories. The default locale (`en-US`) uses a very simple `plural()` function that doesn't support the `few` pluralization category. Another locale with a simple `plural()` function is `es`. The following code example shows the [en-US `plural()`](https://github.com/angular/angular/blob/ecffc3557fe1bff9718c01277498e877ca44588d/packages/core/src/i18n/locale_en.ts#L14-L18 "Line") function. function plural(n: number): number { let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length; if (i === 1 && v === 0) return 1; return 5;} The `plural()` function only returns 1 (`one`) or 5 (`other`). The `few` category never matches. #### [`minutes` example](https://angular.dev/#minutes-example) If you want to display the following phrase in English, where `x` is a number. updated x minutes ago And you also want to display the following phrases based on the cardinality of `x`. updated just now updated one minute ago Use HTML markup and [interpolations](https://angular.dev/guide/templates/binding#render-dynamic-text-with-text-interpolation). The following code example shows how to use the `plural` clause to express the previous three situations in a `<span>` element. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> Review the following details in the previous code example. | Parameters | Details | | --- | --- | | `minutes` | The first parameter specifies the component property is `minutes` and determines the number of minutes. | | `plural` | The second parameter specifies the ICU clause is `plural`. | | `=0 {just now}` | For zero minutes, the pluralization category is `=0`. The value is `just now`. | | `=1 {one minute}` | For one minute, the pluralization category is `=1`. The value is `one minute`. | | `other {{{minutes}} minutes ago}` | For any unmatched cardinality, the default pluralization category is `other`. The value is `{{minutes}} minutes ago`. | `{{minutes}}` is an [interpolation](https://angular.dev/guide/templates/binding#render-dynamic-text-with-text-interpolation). ### [Mark alternates and nested expressions](https://angular.dev/#mark-alternates-and-nested-expressions) The `select` clause marks choices for alternate text based on your defined string values. { component_property, select, selection_categories } Translate all of the alternates to display alternate text based on the value of a variable. After the selection category, enter the text (English) surrounded by open curly brace (`{`) and close curly brace (`}`) characters. selection_category { text } Different locales have different grammatical constructions that increase the difficulty of translation. Use HTML markup. If none of the selection categories match, Angular uses `other` to match the standard fallback for a missing category. other { default_value } #### [`gender` example](https://angular.dev/#gender-example) If you want to display the following phrase in English. The author is other And you also want to display the following phrases based on the `gender` property of the component. The author is female The author is male The following code example shows how to bind the `gender` property of the component and use the `select` clause to express the previous three situations in a `<span>` element. The `gender` property binds the outputs to each of following string values. | Value | English value | | --- | --- | | female | `female` | | male | `male` | | other | `other` | The `select` clause maps the values to the appropriate translations. The following code example shows `gender` property used with the select clause. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> #### [`gender` and `minutes` example](https://angular.dev/#gender-and-minutes-example) Combine different clauses together, such as the `plural` and `select` clauses. The following code example shows nested clauses based on the `gender` and `minutes` examples. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> ## [What's next](https://angular.dev/#whats-next) [Work with translation files](https://angular.dev/guide/i18n/translation-files) --- ## Page: https://angular.dev/guide/i18n/translation-files After you prepare a component for translation, use the [`extract-i18n`](https://angular.dev/cli/extract-i18n "ng") [Angular CLI](https://angular.dev/cli "CLI") command to extract the marked text in the component into a _source language_ file. The marked text includes text marked with `i18n`, attributes marked with `i18n-`_attribute_, and text tagged with `$localize` as described in [Prepare component for translation](https://angular.dev/guide/i18n/prepare "Prepare"). Complete the following steps to create and update translation files for your project. 1. [Extract the source language file](https://angular.dev/guide/i18n/translation-files#extract-the-source-language-file "Extract"). 1. Optionally, change the location, format, and name. 2. Copy the source language file to [create a translation file for each language](https://angular.dev/guide/i18n/translation-files#create-a-translation-file-for-each-language "Create"). 3. [Translate each translation file](https://angular.dev/guide/i18n/translation-files#translate-each-translation-file "Translate"). 4. Translate plurals and alternate expressions separately. 1. [Translate plurals](https://angular.dev/guide/i18n/translation-files#translate-plurals "Translate"). 2. [Translate alternate expressions](https://angular.dev/guide/i18n/translation-files#translate-alternate-expressions "Translate"). 3. [Translate nested expressions](https://angular.dev/guide/i18n/translation-files#translate-nested-expressions "Translate"). To extract the source language file, complete the following actions. 1. Open a terminal window. 2. Change to the root directory of your project. 3. Run the following CLI command. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr The `extract-i18n` command creates a source language file named `messages.xlf` in the root directory of your project. For more information about the XML Localization Interchange File Format (XLIFF, version 1.2), see [XLIFF](https://en.wikipedia.org/wiki/XLIFF "XLIFF"). Use the following [`extract-i18n`](https://angular.dev/cli/extract-i18n "ng") command options to change the source language file location, format, and file name. | Command option | Details | | --- | --- | | `--format` | Set the format of the output file | | `--out-file` | Set the name of the output file | | `--output-path` | Set the path of the output directory | ### [Change the source language file location](https://angular.dev/#change-the-source-language-file-location) To create a file in the `src/locale` directory, specify the output path as an option. The following example specifies the output path as an option. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr ### [Change the source language file format](https://angular.dev/#change-the-source-language-file-format) The `extract-i18n` command creates files in the following translation formats. | Translation format | Details | File extension | | --- | --- | --- | | ARB | [Application Resource Bundle](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification "ApplicationResourceBundleSpecification") | `.arb` | | JSON | [JavaScript Object Notation](https://www.json.org/ "Introducing") | `.json` | | XLIFF 1.2 | [XML Localization Interchange File Format, version 1.2](http://docs.oasis-open.org/xliff/xliff-core/xliff-core.html "XLIFF") | `.xlf` | | XLIFF 2 | [XML Localization Interchange File Format, version 2](http://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html "XLIFF") | `.xlf` | | XMB | [XML Message Bundle](http://cldr.unicode.org/development/development-process/design-proposals/xmb "XMB") | `.xmb` (`.xtb`) | Specify the translation format explicitly with the `--format` command option. **HELPFUL:** The XMB format generates `.xmb` source language files, but uses`.xtb` translation files. The following example demonstrates several translation formats. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr ### [Change the source language file name](https://angular.dev/#change-the-source-language-file-name) To change the name of the source language file generated by the extraction tool, use the `--out-file` command option. The following example demonstrates naming the output file. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr ## [Create a translation file for each language](https://angular.dev/#create-a-translation-file-for-each-language) To create a translation file for a locale or language, complete the following actions. 1. [Extract the source language file](https://angular.dev/guide/i18n/translation-files#extract-the-source-language-file "Extract"). 2. Make a copy of the source language file to create a _translation_ file for each language. 3. Rename the _translation_ file to add the locale. messages.xlf --> messages.{locale}.xlf 4. Create a new directory at your project root named `locale`. src/locale 5. Move the _translation_ file to the new directory. 6. Send the _translation_ file to your translator. 7. Repeat the above steps for each language you want to add to your application. For example, to create a French translation file, complete the following actions. 1. Run the `extract-i18n` command. 2. Make a copy of the `messages.xlf` source language file. 3. Rename the copy to `messages.fr.xlf` for the French language (`fr`) translation. 4. Move the `fr` translation file to the `src/locale` directory. 5. Send the `fr` translation file to the translator. ## [Translate each translation file](https://angular.dev/#translate-each-translation-file) Unless you are fluent in the language and have the time to edit translations, you will likely complete the following steps. 1. Send each translation file to a translator. 2. The translator uses an XLIFF file editor to complete the following actions. 1. Create the translation. 2. Edit the translation. ### [Translation process example for French](https://angular.dev/#translation-process-example-for-french) To demonstrate the process, review the `messages.fr.xlf` file in the [Example Angular Internationalization application](https://angular.dev/guide/i18n/example "Example"). The [Example Angular Internationalization application](https://angular.dev/guide/i18n/example "Example") includes a French translation for you to edit without a special XLIFF editor or knowledge of French. The following actions describe the translation process for French. 1. Open `messages.fr.xlf` and find the first `<trans-unit>` element. This is a _translation unit_, also known as a _text node_, that represents the translation of the `<h1>` greeting tag that was previously marked with the `i18n` attribute. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> The `id="introductionHeader"` is a [custom ID](https://angular.dev/guide/i18n/manage-marked-text "Manage"), but without the `@@` prefix required in the source HTML. 2. Duplicate the `<source>... </source>` element in the text node, rename it to `target`, and then replace the content with the French text. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> In a more complex translation, the information and context in the [description and meaning elements](https://angular.dev/guide/i18n/prepare#add-helpful-descriptions-and-meanings "Add") help you choose the right words for translation. 3. Translate the other text nodes. The following example displays the way to translate. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> **IMPORTANT:** Don't change the IDs for translation units. Each `id` attribute is generated by Angular and depends on the content of the component text and the assigned meaning. If you change either the text or the meaning, then the `id` attribute changes. For more about managing text updates and IDs, see [custom IDs](https://angular.dev/guide/i18n/manage-marked-text "Manage"). ## [Translate plurals](https://angular.dev/#translate-plurals) Add or remove plural cases as needed for each language. ### [`minute` `plural` example](https://angular.dev/#minute-plural-example) To translate a `plural`, translate the ICU format match values. * `just now` * `one minute ago` * `<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago` The following example displays the way to translate. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> ## [Translate alternate expressions](https://angular.dev/#translate-alternate-expressions) Angular also extracts alternate `select` ICU expressions as separate translation units. ### [`gender` `select` example](https://angular.dev/#gender-select-example) The following example displays a `select` ICU expression in the component template. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> In this example, Angular extracts the expression into two translation units. The first contains the text outside of the `select` clause, and uses a placeholder for `select` (`<x id="ICU">`): <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> **IMPORTANT:** When you translate the text, move the placeholder if necessary, but don't remove it. If you remove the placeholder, the ICU expression is removed from your translated application. The following example displays the second translation unit that contains the `select` clause. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> The following example displays both translation units after translation is complete. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> ## [Translate nested expressions](https://angular.dev/#translate-nested-expressions) Angular treats a nested expression in the same manner as an alternate expression. Angular extracts the expression into two translation units. ### [Nested `plural` example](https://angular.dev/#nested-plural-example) The following example displays the first translation unit that contains the text outside of the nested expression. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> The following example displays the second translation unit that contains the complete nested expression. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> The following example displays both translation units after translating. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> ## [What's next](https://angular.dev/#whats-next) [Merge translations into the app](https://angular.dev/guide/i18n/merge) --- ## Page: https://angular.dev/guide/i18n/merge To merge the completed translations into your project, complete the following actions 1. Use the [Angular CLI](https://angular.dev/cli "CLI") to build a copy of the distributable files of your project 2. Use the `"localize"` option to replace all of the i18n messages with the valid translations and build a localized variant application. A variant application is a complete a copy of the distributable files of your application translated for a single locale. After you merge the translations, serve each distributable copy of the application using server-side language detection or different subdirectories. **HELPFUL:** For more information about how to serve each distributable copy of the application, see [deploying multiple locales](https://angular.dev/guide/i18n/deploy). For a compile-time translation of the application, the build process uses ahead-of-time (AOT) compilation to produce a small, fast, ready-to-run application. **HELPFUL:** For a detailed explanation of the build process, see [Building and serving Angular apps](https://angular.dev/tools/cli/build "Building"). The build process works for translation files in the `.xlf` format or in another format that Angular understands, such as `.xtb`. For more information about translation file formats used by Angular, see [Change the source language file format](https://angular.dev/guide/i18n/translation-files#change-the-source-language-file-format "Change") To build a separate distributable copy of the application for each locale, [define the locales in the build configuration](https://angular.dev/guide/i18n/merge#define-locales-in-the-build-configuration "Define") in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file of your project. This method shortens the build process by removing the requirement to perform a full application build for each locale. To [generate application variants for each locale](https://angular.dev/guide/i18n/merge#generate-application-variants-for-each-locale "Generate"), use the `"localize"` option in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file. Also, to [build from the command line](https://angular.dev/guide/i18n/merge#build-from-the-command-line "Build"), use the [`build`](https://angular.dev/cli/build "ng") [Angular CLI](https://angular.dev/cli "CLI") command with the `--localize` option. ## [Define locales in the build configuration](https://angular.dev/#define-locales-in-the-build-configuration) Use the `i18n` project option in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file of your project to define locales for a project. The following sub-options identify the source language and tell the compiler where to find supported translations for the project. | Suboption | Details | | --- | --- | | `sourceLocale` | The locale you use within the application source code (`en-US` by default) | | `locales` | A map of locale identifiers to translation files | ### [`angular.json` for `en-US` and `fr` example](https://angular.dev/#angularjson-for-en-us-and-fr-example) For example, the following excerpt of an [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file sets the source locale to `en-US` and provides the path to the French (`fr`) locale translation file. { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} ## [Generate application variants for each locale](https://angular.dev/#generate-application-variants-for-each-locale) To use your locale definition in the build configuration, use the `"localize"` option in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file to tell the CLI which locales to generate for the build configuration. * Set `"localize"` to `true` for all the locales previously defined in the build configuration. * Set `"localize"` to an array of a subset of the previously defined locale identifiers to build only those locale versions. * Set `"localize"` to `false` to disable localization and not generate any locale-specific versions. **HELPFUL:** Ahead-of-time (AOT) compilation is required to localize component templates. If you changed this setting, set `"aot"` to `true` in order to use AOT. **HELPFUL:** Due to the deployment complexities of i18n and the need to minimize rebuild time, the development server only supports localizing a single locale at a time. If you set the `"localize"` option to `true`, define more than one locale, and use `ng serve`; then an error occurs. If you want to develop against a specific locale, set the `"localize"` option to a specific locale. For example, for French (`fr`), specify `"localize": ["fr"]`. The CLI loads and registers the locale data, places each generated version in a locale-specific directory to keep it separate from other locale versions, and puts the directories within the configured `outputPath` for the project. For each application variant the `lang` attribute of the `html` element is set to the locale. The CLI also adjusts the HTML base HREF for each version of the application by adding the locale to the configured `baseHref`. Set the `"localize"` property as a shared configuration to effectively inherit for all the configurations. Also, set the property to override other configurations. ### [`angular.json` include all locales from build example](https://angular.dev/#angularjson-include-all-locales-from-build-example) The following example displays the `"localize"` option set to `true` in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file, so that all locales defined in the build configuration are built. { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} ## [Build from the command line](https://angular.dev/#build-from-the-command-line) Also, use the `--localize` option with the [`ng build`](https://angular.dev/cli/build "ng") command and your existing `production` configuration. The CLI builds all locales defined in the build configuration. If you set the locales in build configuration, it is similar to when you set the `"localize"` option to `true`. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr ## [Apply specific build options for just one locale](https://angular.dev/#apply-specific-build-options-for-just-one-locale) To apply specific build options to only one locale, specify a single locale to create a custom locale-specific configuration. **IMPORTANT:** Use the [Angular CLI](https://angular.dev/cli "CLI") development server (`ng serve`) with only a single locale. ### [build for French example](https://angular.dev/#build-for-french-example) The following example displays a custom locale-specific configuration using a single locale. { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} Pass this configuration to the `ng serve` or `ng build` commands. The following code example displays how to serve the French language file. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr For production builds, use configuration composition to run both configurations. ng add @angular/localizeng extract-i18nng extract-i18n --output-path src/localeng extract-i18n --format=xlfng extract-i18n --format=xlf2ng extract-i18n --format=xmbng extract-i18n --format=jsonng extract-i18n --format=arbng extract-i18n --out-file source.xlfng build --localizeng serve --configuration=frng build --configuration=production,fr { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} ## [Report missing translations](https://angular.dev/#report-missing-translations) When a translation is missing, the build succeeds but generates a warning such as `Missing translation for message "{translation_text}"`. To configure the level of warning that is generated by the Angular compiler, specify one of the following levels. | Warning level | Details | Output | | --- | --- | --- | | `error` | Throw an error and the build fails | n/a | | `ignore` | Do nothing | n/a | | `warning` | Displays the default warning in the console or shell | `Missing translation for message "{translation_text}"` | Specify the warning level in the `options` section for the `build` target of your [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file. ### [`angular.json` `error` warning example](https://angular.dev/#angularjson-error-warning-example) The following example displays how to set the warning level to `error`. { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} **HELPFUL:** When you compile your Angular project into an Angular application, the instances of the `i18n` attribute are replaced with instances of the [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string. This means that your Angular application is translated after compilation. This also means that you can create localized versions of your Angular application without re-compiling your entire Angular project for each locale. When you translate your Angular application, the _translation transformation_ replaces and reorders the parts (static strings and expressions) of the template literal string with strings from a collection of translations. For more information, see [`$localize`](https://angular.dev/api/localize/init/$localize "$localize"). **TL;DR:** Compile once, then translate for each locale. ## [What's next](https://angular.dev/#whats-next) [Deploy multiple locales](https://angular.dev/guide/i18n/deploy) --- ## Page: https://angular.dev/guide/i18n/deploy If `myapp` is the directory that contains the distributable files of your project, you typically make different versions available for different locales in locale directories. For example, your French version is located in the `myapp/fr` directory and the Spanish version is located in the `myapp/es` directory. The HTML `base` tag with the `href` attribute specifies the base URI, or URL, for relative links. If you set the `"localize"` option in [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file to `true` or to an array of locale IDs, the CLI adjusts the base `href` for each version of the application. To adjust the base `href` for each version of the application, the CLI adds the locale to the configured `"subPath"`. Specify the `"subPath"` for each locale in your [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file. The following example displays `"subPath"` set to an empty string. { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular.io-example": { "projectType": "application", "root": "", "sourceRoot": "src", "prefix": "app", "i18n": { "sourceLocale": "en-US", "locales": { "fr": { "translation": "src/locale/messages.fr.xlf", "subPath": "" } } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "localize": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [], "i18nMissingTranslation": "error" }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "localize": false, "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "fr": { "localize": ["fr"] } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular.io-example:build:production" }, "development": { "buildTarget": "angular.io-example:build:development" }, "fr": { "buildTarget": "angular.io-example:build:development,fr" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular.io-example:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "assets": ["src/favicon.ico", "src/assets"], "styles": ["src/styles.css"], "scripts": [] } }, "e2e": { "builder": "@angular-devkit/build-angular:private-protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular.io-example:serve:fr" }, "configurations": { "production": { "devServerTarget": "angular.io-example:serve:production" } } } } } }} Typical deployment of multiple languages serve each language from a different subdirectory. Users are redirected to the preferred language defined in the browser using the `Accept-Language` HTTP header. If the user has not defined a preferred language, or if the preferred language is not available, then the server falls back to the default language. To change the language, change your current location to another subdirectory. The change of subdirectory often occurs using a menu implemented in the application. **IMPORTANT:** If you are using [Hybrid rendering](https://angular.dev/guide/hybrid-rendering) with `outputMode` set to `server`, Angular automatically handles redirection dynamically based on the `Accept-Language` HTTP header. This simplifies deployment by eliminating the need for manual server or configuration adjustments. The following example displays an Nginx configuration. http { # Browser preferred language detection (does NOT require # AcceptLanguageModule) map $http_accept_language $accept_language { ~*^de de; ~*^fr fr; ~*^en ""; } # ...}server { listen 80; server_name localhost; root /www/data; # Fallback to default language if no preference defined by browser if ($accept_language ~ "^$") { set $accept_language "fr"; } # Redirect "/" to Angular application in the preferred language of the browser rewrite ^/$ /$accept_language permanent; # Everything under the Angular application is always redirected to Angular in the # correct language location ~ ^/(fr|de|$) { if ($accept_language = "") { try_files $uri /index.html?$args; } try_files $uri /$1/index.html?$args; } # ...} The following example displays an Apache configuration. # docregion<VirtualHost *:80> ServerName localhost DocumentRoot /www/data <Directory "/www/data"> # Enable rewrite engine for URL manipulation RewriteEngine on RewriteBase / # Serve 'index.html' for root-level access RewriteRule ^../index\.html$ - [L] # If the requested file or directory does not exist, redirect to 'index.html' RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (..) $1/index.html [L] # Language-based redirection based on HTTP Accept-Language header # Redirect German users to the '/de/' directory RewriteCond %{HTTP:Accept-Language} ^de [NC] RewriteRule ^$ /de/ [R] # Redirect English-speaking users to the '/en/' directory RewriteCond %{HTTP:Accept-Language} ^en [NC] RewriteRule ^$ /en/ [R] # Redirect users with unsupported languages (not 'en' or 'de') to the '/fr/' directory RewriteCond %{HTTP:Accept-Language} !^en [NC] RewriteCond %{HTTP:Accept-Language} !^de [NC] RewriteRule ^$ /fr/ [R] </Directory></VirtualHost> --- ## Page: https://angular.dev/guide/i18n/import-global-variants The [Angular CLI](https://angular.dev/cli "CLI") automatically includes locale data if you run the [`ng build`](https://angular.dev/cli/build "ng") command with the `--localize` option. ng build --localize **HELPFUL:** The initial installation of Angular already contains locale data for English in the United States (`en-US`). The [Angular CLI](https://angular.dev/cli "CLI") automatically includes the locale data and sets the `LOCALE_ID` value when you use the `--localize` option with [`ng build`](https://angular.dev/cli/build "ng") command. The `@angular/common` package on npm contains the locale data files. Global variants of the locale data are available in `@angular/common/locales/global`. ## [`import` example for French](https://angular.dev/#import-example-for-french) For example, you could import the global variants for French (`fr`) in `main.ts` where you bootstrap the application. import '@angular/common/locales/global/fr';import {provideProtractorTestingSupport} from '@angular/platform-browser';import {bootstrapApplication} from '@angular/platform-browser';import {AppComponent} from './app/app.component';bootstrapApplication(AppComponent, { providers: [ provideProtractorTestingSupport(), // essential for e2e testing ],}); **HELPFUL:** In an `NgModules` application, you would import it in your `app.module`. --- ## Page: https://angular.dev/guide/i18n/manage-marked-text The Angular extractor generates a file with a translation unit entry each of the following instances. * Each `i18n` attribute in a component template * Each [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string in component code As described in [How meanings control text extraction and merges](https://angular.dev/guide/i18n/prepare#h1-example "How"), Angular assigns each translation unit a unique ID. The following example displays translation units with unique IDs. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> When you change the translatable text, the extractor generates a new ID for that translation unit. In most cases, changes in the source text also require a change to the translation. Therefore, using a new ID keeps the text change in sync with translations. However, some translation systems require a specific form or syntax for the ID. To address the requirement, use a custom ID to mark text. Most developers don't need to use a custom ID. If you want to use a unique syntax to convey additional metadata, use a custom ID. Additional metadata may include the library, component, or area of the application in which the text appears. To specify a custom ID in the `i18n` attribute or [`$localize`](https://angular.dev/api/localize/init/$localize "$localize") tagged message string, use the `@@` prefix. The following example defines the `introductionHeader` custom ID in a heading element. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The following example defines the `introductionHeader` custom ID for a variable. variableText1 = $localize`:@@introductionHeader:Hello i18n!`; When you specify a custom ID, the extractor generates a translation unit with the custom ID. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> If you change the text, the extractor does not change the ID. As a result, you don't have to take the extra step to update the translation. The drawback of using custom IDs is that if you change the text, your translation may be out-of-sync with the newly changed source text. ## [Use a custom ID with a description](https://angular.dev/#use-a-custom-id-with-a-description) Use a custom ID in combination with a description and a meaning to further help the translator. The following example includes a description, followed by the custom ID. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The following example defines the `introductionHeader` custom ID and description for a variable. variableText2 = $localize`:An introduction header for this sample@@introductionHeader:Hello i18n!`; The following example adds a meaning. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The following example defines the `introductionHeader` custom ID for a variable. variableText3 = $localize`:site header|An introduction header for this sample@@introductionHeader:Hello i18n!`; ### [Define unique custom IDs](https://angular.dev/#define-unique-custom-ids) Be sure to define custom IDs that are unique. If you use the same ID for two different text elements, the extraction tool extracts only the first one, and Angular uses the translation in place of both original text elements. For example, in the following code snippet the same `myId` custom ID is defined for two different text elements. <h1>Hello i18n!</h1><h1 i18n>Hello i18n!</h1><h1 i18n="An introduction header for this sample">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1><h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1><h1 i18n="@@introductionHeader">Hello i18n!</h1><img [src]="logo" title="Angular logo" alt="Angular logo"><h3 i18n="@@myId">Hello</h3><!-- ... --><p i18n="@@myId">Good bye</p> The following displays the translation in French. <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> Both elements now use the same translation (`Bonjour`), because both were defined with the same custom ID. <h3>Bonjour</h3><!-- ... --><p>Bonjour</p> --- ## Page: https://angular.dev/guide/i18n/example <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1><ng-container i18n>I don't output any element</ng-container><br /><img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/><br><button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span>({{ minutes }})<br><br><button type="button" (click)="male()">♂</button><button type="button" (click)="female()">♀</button><button type="button" (click)="other()">⚧</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span><br><br><button type="button" (click)="toggleDisplay()">Toggle</button><div i18n [attr.aria-label]="toggleAriaLabel()">{{toggle()}}</div> import {Component, computed, signal} from '@angular/core';import {$localize} from '@angular/localize/init';@Component({ selector: 'app-root', templateUrl: './app.component.html',})export class AppComponent { minutes = 0; gender = 'female'; fly = true; logo = '${this.baseUrl}/angular.svg'; toggle = signal(false); toggleAriaLabel = computed(() => { return this.toggle() ? $localize`:Toggle Button|A button to toggle status:Show` : $localize`:Toggle Button|A button to toggle status:Hide`; }); inc(i: number) { this.minutes = Math.min(5, Math.max(0, this.minutes + i)); } male() { this.gender = 'male'; } female() { this.gender = 'female'; } other() { this.gender = 'other'; } toggleDisplay() { this.toggle.update((toggle) => !toggle); }} import {bootstrapApplication} from '@angular/platform-browser';import {AppComponent} from './app/app.component';bootstrapApplication(AppComponent); <!-- The `messages.fr.xlf` after translation for documentation purposes --><?xml version="1.0" encoding="UTF-8" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="ng2.template"> <body> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don't output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> <trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit> </body> </file></xliff> --- ## Page: https://angular.dev/guide/i18n/cli ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/reference/configs/npm-packages ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/locale-id ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/reference/configs/workspace-config ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/format-data-locale ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/templates/pipes ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/core/LOCALE_ID ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/common/DatePipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/common/CurrencyPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/common/DecimalPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/common/PercentPipe ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/prepare ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/api/localize/init/$localize ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/templates/binding#render-dynamic-text-with-text-interpolation ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/manage-marked-text ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/translation-files ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/cli/extract-i18n ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/example ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/merge ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/i18n/deploy ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/tools/cli/build ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/cli/build ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/tools/cli/deployment ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/i18n/guide/hybrid-rendering ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/experimental/zoneless ## [Why use Zoneless?](https://angular.dev/#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](https://angular.dev/#enabling-zoneless-in-an-application) The API for enabling Zoneless is currently experimental. Neither the shape, nor the underlying behavior is stable and can change in patch versions. There are known feature gaps, including the lack of an ergonomic API which prevents the application from serializing too early with Server Side Rendering. // standalone bootstrapbootstrapApplication(MyApp, {providers: [ provideExperimentalZonelessChangeDetection(),]});// NgModule bootstrapplatformBrowser().bootstrapModule(AppModule);@NgModule({ providers: [provideExperimentalZonelessChangeDetection()]})export class AppModule {} ## [Removing ZoneJS](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#onpush-compatible-components) One way to ensure that a component is using the correct notification mechanisms from above is to use [ChangeDetectionStrategy.OnPush](https://angular.dev/best-practices/skipping-subtrees#using-onpush). The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use `ChangeDetectionStrategy.OnPush`. 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`](https://angular.dev/#remove-ngzoneonmicrotaskempty-ngzoneonunstable-ngzoneisstable-or-ngzoneonstable) 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 `afterRender` if there is some condition that might span several change detection rounds. In other cases, these observables were used because they happened to be familiar and have similar timing to what was needed. More straightforward or direct DOM APIs can be used instead, such as `MutationObserver` when code needs to wait for certain DOM state (rather than waiting for it indirectly through Angular's render hooks). ### NgZone.run and NgZone.runOutsideAngular are compatible with Zoneless `NgZone.run` 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)](https://angular.dev/#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](https://angular.dev/api/core/PendingTasks) service. Serialization will wait for the first moment that all pending tasks have been removed. The two most straightforward uses of pending tasks are the `run` method: const taskService = inject(PendingTasks);taskService.run(async () => { const someResult = await doSomeWorkThatNeedsToBeRendered(); this.someState.set(someResult);}); For more complicated use-cases, you can manuall add and remove a pending tasks: const taskService = inject(PendingTasks);const taskCleanup = taskService.add();try { await doSomeWorkThatNeedsToBeRendered();} catch { // handle error} finally { taskCleanup();} In addition, the [pendingUntilEvent](https://angular.dev/api/core/rxjs-interop/pendingUntilEvent#) helper in `rxjs-interop` ensures the application remains unstable until the observable emits, complets, 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](https://angular.dev/#testing-and-debugging) ### [Using Zoneless in `TestBed`](https://angular.dev/#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: [provideExperimentalZonelessChangeDetection()]});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](https://angular.dev/#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. `provideExperimentalCheckNoChangesForDebug` 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. --- ## Page: https://angular.dev/guide/animations/css CSS offers a robust set of tools for you to create beautiful and engaging animations within your application. ## [How to write animations in native CSS](https://angular.dev/#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](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) [W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp) [The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/) [CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners) and a couple of videos: [Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw) [Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5) Check some of these various guides and tutorials out, and then come back to this guide. ## [Creating Reusable Animations](https://angular.dev/#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. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Adding the class `animated-class` to an element would trigger the animation on that element. ## [Animating a Transition](https://angular.dev/#animating-a-transition) ### [Animating State and Styles](https://angular.dev/#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. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Triggering 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](https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings). You can see similar examples in the template guide for [animating styles directly](https://angular.dev/guide/templates/binding#css-style-properties). ### [Transitions, Timing, and Easing](https://angular.dev/#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. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} ### [Triggering an Animation](https://angular.dev/#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: import {Component, signal} from '@angular/core';@Component({ selector: 'app-open-close', templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <h2>Open / Close Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="open-close-container" [class.open]="isOpen()"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p></div> :host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; font-weight: bold; font-size: 20px; height: 100px; opacity: 0.8; background-color: blue; color: #ebebeb; transition-property: height, opacity, background-color, color; transition-duration: 1s;}.open { transition-duration: 0.5s; height: 200px; opacity: 1; background-color: yellow; color: #000000;} ## [Transition and Triggers](https://angular.dev/#transition-and-triggers) ### [Animating Auto Height](https://angular.dev/#animating-auto-height) You can use css-grid to animate to auto height. import {Component, signal} from '@angular/core';@Component({ selector: 'app-auto-height', templateUrl: 'auto-height.component.html', styleUrls: ['auto-height.component.css'],})export class AutoHeightComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <h2>Auto Height Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="container" [class.open]="isOpen()"> <div class="content"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p> </div></div> .container { display: grid; grid-template-rows: 0fr; overflow: hidden; transition: grid-template-rows 1s;}.container.open { grid-template-rows: 1fr;}.container .content { min-height: 0; transition: visibility 1s; padding: 0 20px; visibility: hidden; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb; overflow: hidden;}.container.open .content { visibility: visible;} If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)\[[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/\]](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](https://angular.dev/#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 leaving a view. import {Component, signal} from '@angular/core';@Component({ selector: 'app-insert', templateUrl: 'insert.component.html', styleUrls: ['insert.component.css'],})export class InsertComponent { isShown = signal(false); toggle() { this.isShown.update((isShown) => !isShown); }} <h2>Insert Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container"> <p>The box is inserted</p> </div>} :host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px; opacity: 1; transition: opacity 1s ease-out, transform 1s ease-out; @starting-style { opacity: 0; transform: translateY(20px); }} Leaving a view is slightly more complex. The element removal needs to be delayed until the exit animation is complete. This requires a bit of extra code in your component class to accomplish. import {Component, ElementRef, inject, signal} from '@angular/core';@Component({ selector: 'app-remove', templateUrl: 'remove.component.html', styleUrls: ['remove.component.css'],})export class RemoveComponent { isShown = signal(false); deleting = signal(false); private el = inject(ElementRef); toggle() { if (this.isShown()) { const target = this.el.nativeElement.querySelector('.insert-container'); target.addEventListener('transitionend', () => this.hide()); this.deleting.set(true); } else { this.isShown.update((isShown) => !isShown); } } hide() { this.isShown.set(false); this.deleting.set(false); }} <h2>Remove Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container" [class.deleting]="deleting()"> <p>The box is inserted</p> </div>} :host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px; opacity: 1; transition: opacity 200ms ease-in; @starting-style { opacity: 0; }}.deleting { opacity: 0; transform: translateY(20px); transition: opacity 500ms ease-out, transform 500ms ease-out;} ### [Animating increment and decrement](https://angular.dev/#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. import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';@Component({ selector: 'app-increment-decrement', templateUrl: 'increment-decrement.component.html', styleUrls: ['increment-decrement.component.css'],})export class IncrementDecrementComponent implements OnInit { num = signal(0); el = viewChild<ElementRef<HTMLParagraphElement>>('el'); ngOnInit() { this.el()?.nativeElement.addEventListener('animationend', (ev) => { if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) { this.animationFinished(); } }); } modify(n: number) { const targetClass = n > 0 ? 'increment' : 'decrement'; this.num.update((v) => (v += n)); this.el()?.nativeElement.classList.add(targetClass); } animationFinished() { this.el()?.nativeElement.classList.remove('increment', 'decrement'); } ngOnDestroy() { this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished); }} <h3>Increment and Decrement Example</h3><section> <p #el>Number {{ num() }}</p> <div class="controls"> <button type="button" (click)="modify(1)">+</button> <button type="button" (click)="modify(-1)">-</button> </div></section> :host { display: block; font-size: 32px; margin: 20px; text-align: center;}section { border: 1px solid lightgray; border-radius: 50px;}p { display: inline-block; margin: 2rem 0; text-transform: uppercase;}.increment { animation: increment 300ms;}.decrement { animation: decrement 300ms;}.controls { padding-bottom: 2rem;}button { font: inherit; border: 0; background: lightgray; width: 50px; border-radius: 10px;}button + button { margin-left: 10px;}@keyframes increment { 33% { color: green; transform: scale(1.3, 1.2); } 66% { color: green; transform: scale(1.2, 1.2); } 100% { transform: scale(1, 1); }}@keyframes decrement { 33% { color: red; transform: scale(0.8, 0.9); } 66% { color: red; transform: scale(0.9, 0.9); } 100% { transform: scale(1, 1); }} ### [Disabling an animation or all animations](https://angular.dev/#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`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation. 2. Prevent adding animation classes programatically ### [Animation Callbacks](https://angular.dev/#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`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event) [`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event) [`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event) [`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event) [`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event) [`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event) [`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event) [`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event) The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) 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](https://angular.dev/#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](https://angular.dev/#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. import {Component, signal} from '@angular/core';@Component({ selector: 'app-stagger', templateUrl: './stagger.component.html', styleUrls: ['stagger.component.css'],})export class StaggerComponent { show = signal(true); items = [1, 2, 3]; refresh() { this.show.set(false); setTimeout(() => { this.show.set(true); }, 10); }} <h1>Stagger Example</h1><button type="button" (click)="refresh()">Refresh</button>@if (show()) { <ul class="items"> @for(item of items; track item) { <li class="item" style="--index: {{ item }}">{{item}}</li> } </ul>} .items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; transition-delay: calc(200ms * var(--index)); @starting-style { opacity: 0; transform: translateX(-10px); }} ### [Parallel Animations](https://angular.dev/#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](https://angular.dev/#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. Removal animations will require additional code to add the event listener, as seen in the example above. import {Component, signal} from '@angular/core';@Component({ selector: 'app-reorder', templateUrl: './reorder.component.html', styleUrls: ['reorder.component.css'],})export class ReorderComponent { show = signal(true); items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock']; randomize() { const randItems = [...this.items]; const newItems = []; for (let i of this.items) { const max: number = this.items.length - newItems.length; const randNum = Math.floor(Math.random() * max); newItems.push(...randItems.splice(randNum, 1)); } this.items = newItems; }} <h1>Reordering List Example</h1><button type="button" (click)="randomize()">Randomize</button><ul class="items"> @for(item of items; track item) { <li class="item">{{ item }}</li> }</ul> .items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; @starting-style { opacity: 0; transform: translateX(-10px); }} ## [Programmatic control of animations](https://angular.dev/#programmatic-control-of-animations) You can retrieve animations off an element directly using [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the `AnimationPlayer` 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. --- ## Page: https://angular.dev/tools/cli The Angular CLI is a command-line interface tool which allows you to scaffold, develop, test, deploy, and maintain Angular applications directly from a command shell. Angular CLI is published on npm as the `@angular/cli` package and includes a binary named `ng`. Commands invoking `ng` are using the Angular CLI. ### Try Angular without local setup If you are new to Angular, you might want to start with [Try it now!](https://angular.dev/tutorials/learn-angular), which introduces the essentials of Angular in the context of a ready-made basic online store app for you to examine and modify. This standalone tutorial takes advantage of the interactive [StackBlitz](https://stackblitz.com/) environment for online development. You don't need to set up your local environment until you're ready. ## [CLI command-language syntax](https://angular.dev/#cli-command-language-syntax) Angular CLI roughly follows Unix/POSIX conventions for option syntax. ### [Boolean options](https://angular.dev/#boolean-options) Boolean options have two forms: `--this-option` sets the flag to `true`, `--no-this-option` sets it to `false`. You can also use `--this-option=false` or `--this-option=true`. If neither option is supplied, the flag remains in its default state, as listed in the reference documentation. ### [Array options](https://angular.dev/#array-options) Array options can be provided in two forms: `--option value1 value2` or `--option value1 --option value2`. ### [Key/value options](https://angular.dev/#key-value-options) Some options like `--define` expect an array of `key=value` pairs as their values. Just like array options, key/value options can be provided in two forms: `--define 'KEY_1="value1"' KEY_2=true` or `--define 'KEY_1="value1"' --define KEY_2=true`. ### [Relative paths](https://angular.dev/#relative-paths) Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root. --- ## Page: https://angular.dev/tools/cli/setup-local This guide explains how to set up your environment for Angular development using the [Angular CLI](https://angular.dev/cli "CLI"). It includes information about installing the CLI, creating an initial workspace and starter app, and running that app locally to verify your setup. ### Try Angular without local setup If you are new to Angular, you might want to start with [Try it now!](https://angular.dev/tutorials/learn-angular), which introduces the essentials of Angular in your browser. This standalone tutorial takes advantage of the interactive [StackBlitz](https://stackblitz.com/) environment for online development. You don't need to set up your local environment until you're ready. ## [Before you start](https://angular.dev/#before-you-start) To use Angular CLI, you should be familiar with the following: [JavaScript](https://developer.mozilla.org/docs/Web/JavaScript/A_re-introduction_to_JavaScript) [HTML](https://developer.mozilla.org/docs/Learn/HTML/Introduction_to_HTML) [CSS](https://developer.mozilla.org/docs/Learn/CSS/First_steps) You should also be familiar with usage of command line interface (CLI) tools and have a general understanding of command shells. Knowledge of [TypeScript](https://www.typescriptlang.org/) is helpful, but not required. ## [Dependencies](https://angular.dev/#dependencies) To install Angular CLI on your local system, you need to install [Node.js](https://nodejs.org/). Angular CLI uses Node and its associated package manager, npm, to install and run JavaScript tools outside the browser. [Download and install Node.js](https://nodejs.org/en/download), which will include the `npm` CLI as well. Angular requires an [active LTS or maintenance LTS](https://nodejs.org/en/about/previous-releases) version of Node.js. See [Angular's version compatibility](https://angular.dev/reference/versions) guide for more information. ## [Install the Angular CLI](https://angular.dev/#install-the-angular-cli) To install the Angular CLI, open a terminal window and run the following command: npm install -g @angular/cli pnpm install -g @angular/cli yarn global add @angular/cli bun install -g @angular/cli ### [Powershell execution policy](https://angular.dev/#powershell-execution-policy) On Windows client computers, the execution of PowerShell scripts is disabled by default, so the above command may fail with an error. To allow the execution of PowerShell scripts, which is needed for npm global binaries, you must set the following [execution policy](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_execution_policies): Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned Carefully read the message displayed after executing the command and follow the instructions. Make sure you understand the implications of setting an execution policy. ### [Unix permissions](https://angular.dev/#unix-permissions) On some Unix-like setups, global scripts may be owned by the root user, so to the above command may fail with a permission error. Run with `sudo` to execute the command as the root user and enter your password when prompted: sudo npm install -g @angular/cli sudo pnpm install -g @angular/cli sudo yarn global add @angular/cli sudo bun install -g @angular/cli Make sure you understand the implications of running commands as root. ## [Create a workspace and initial application](https://angular.dev/#create-a-workspace-and-initial-application) You develop apps in the context of an Angular **workspace**. To create a new workspace and initial starter app, run the CLI command `ng new` and provide the name `my-app`, as shown here, then answer prompts about features to include: ng new my-app The Angular CLI installs the necessary Angular npm packages and other dependencies. This can take a few minutes. The CLI creates a new workspace and a small welcome app in a new directory with the same name as the workspace, ready to run. Navigate to the new directory so subsequent commands use this workspace. cd my-app ## [Run the application](https://angular.dev/#run-the-application) The Angular CLI includes a development server, for you to build and serve your app locally. Run the following command: ng serve --open The `ng serve` command launches the server, watches your files, as well as rebuilds the app and reloads the browser as you make changes to those files. The `--open` (or just `-o`) option automatically opens your browser to `http://localhost:4200/` to view the generated application. ## [Workspaces and project files](https://angular.dev/#workspaces-and-project-files) The [`ng new`](https://angular.dev/cli/new) command creates an [Angular workspace](https://angular.dev/reference/configs/workspace-config) folder and generates a new application inside it. A workspace can contain multiple applications and libraries. The initial application created by the [`ng new`](https://angular.dev/cli/new) command is at the root directory of the workspace. When you generate an additional application or library in an existing workspace, it goes into a `projects/` subfolder by default. A newly generated application contains the source files for a root component and template. Each application has a `src` folder that contains its components, data, and assets. You can edit the generated files directly, or add to and modify them using CLI commands. Use the [`ng generate`](https://angular.dev/cli/generate) command to add new files for additional components, directives, pipes, services, and more. Commands such as [`ng add`](https://angular.dev/cli/add) and [`ng generate`](https://angular.dev/cli/generate), which create or operate on applications and libraries, must be executed from within a workspace. By contrast, commands such as `ng new` must be executed _outside_ a workspace because they will create a new one. ## [Next steps](https://angular.dev/#next-steps) * Learn more about the [file structure](https://angular.dev/reference/configs/file-structure) and [configuration](https://angular.dev/reference/configs/workspace-config) of the generated workspace. * Test your new application with [`ng test`](https://angular.dev/cli/test). * Generate boilerplate like components, directives, and pipes with [`ng generate`](https://angular.dev/cli/generate). * Deploy your new application and make it available to real users with [`ng deploy`](https://angular.dev/cli/deploy). * Set up and run end-to-end tests of your application with [`ng e2e`](https://angular.dev/cli/e2e). --- ## Page: https://angular.dev/tools/cli/build You can build your Angular CLI application or library with the `ng build` command. This will compile your TypeScript code to JavaScript, as well as optimize, bundle, and minify the output as appropriate. `ng build` only executes the builder for the `build` target in the default project as specified in `angular.json`. Angular CLI includes four builders typically used as `build` targets: | Builder | Purpose | | --- | --- | | `@angular-devkit/build-angular:application` | Builds an application with a client-side bundle, a Node server, and build-time prerendered routes with [esbuild](https://esbuild.github.io/). | | `@angular-devkit/build-angular:browser-esbuild` | Bundles a client-side application for use in a browser with [esbuild](https://esbuild.github.io/). See [`browser-esbuild` documentation](https://angular.dev/tools/cli/build-system-migration#manual-migration-to-the-compatibility-builder) for more information. | | `@angular-devkit/build-angular:browser` | Bundles a client-side application for use in a browser with [webpack](https://webpack.js.org/). | | `@angular-devkit/build-angular:ng-packagr` | Builds an Angular library adhering to [Angular Package Format](https://angular.dev/tools/libraries/angular-package-format). | Applications generated by `ng new` use `@angular-devkit/build-angular:application` by default. Libraries generated by `ng generate library` use `@angular-devkit/build-angular:ng-packagr` by default. You can determine which builder is being used for a particular project by looking up the `build` target for that project. { "projects": { "my-app": { "architect": { // `ng build` invokes the Architect target named `build`. "build": { "builder": "@angular-devkit/build-angular:application", … }, "serve": { … } "test": { … } … } } }} This page discusses usage and options of `@angular-devkit/build-angular:application`. ## [Output directory](https://angular.dev/#output-directory) The result of this build process is output to a directory (`dist/${PROJECT_NAME}` by default). ## [Configuring size budgets](https://angular.dev/#configuring-size-budgets) As applications grow in functionality, they also grow in size. The CLI lets you set size thresholds in your configuration to ensure that parts of your application stay within size boundaries that you define. Define your size boundaries in the CLI configuration file, `angular.json`, in a `budgets` section for each [configured environment](https://angular.dev/tools/cli/environments). { … "configurations": { "production": { … "budgets": [ { "type": "initial", "maximumWarning": "250kb", "maximumError": "500kb" }, ] } }} You can specify size budgets for the entire app, and for particular parts. Each budget entry configures a budget of a given type. Specify size values in the following formats: | Size value | Details | | --- | --- | | `123` or `123b` | Size in bytes. | | `123kb` | Size in kilobytes. | | `123mb` | Size in megabytes. | | `12%` | Percentage of size relative to baseline. (Not valid for baseline values.) | When you configure a budget, the builder warns or reports an error when a given part of the application reaches or exceeds a boundary size that you set. Each budget entry is a JSON object with the following properties: | Property | Value | | --- | --- | | type | The type of budget. One of: | Value | Details | | --- | --- | | `bundle` | The size of a specific bundle. | | `initial` | The size of JavaScript and CSS needed for bootstrapping the application. Defaults to warning at 500kb and erroring at 1mb. | | `allScript` | The size of all scripts. | | `all` | The size of the entire application. | | `anyComponentStyle` | This size of any one component stylesheet. Defaults to warning at 2kb and erroring at 4kb. | | `anyScript` | The size of any one script. | | `any` | The size of any file. | | | name | The name of the bundle (for `type=bundle`). | | baseline | The baseline size for comparison. | | maximumWarning | The maximum threshold for warning relative to the baseline. | | maximumError | The maximum threshold for error relative to the baseline. | | minimumWarning | The minimum threshold for warning relative to the baseline. | | minimumError | The minimum threshold for error relative to the baseline. | | warning | The threshold for warning relative to the baseline (min & max). | | error | The threshold for error relative to the baseline (min & max). | ## [Configuring CommonJS dependencies](https://angular.dev/#configuring-commonjs-dependencies) Always prefer native [ECMAScript modules](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) (ESM) throughout your application and its dependencies. ESM is a fully specified web standard and JavaScript language feature with strong static analysis support. This makes bundle optimizations more powerful than other module formats. Angular CLI also supports importing [CommonJS](https://nodejs.org/api/modules.html) dependencies into your project and will bundle these dependencies automatically. However, CommonJS modules can prevent bundlers and minifiers from optimizing those modules effectively, which results in larger bundle sizes. For more information, see [How CommonJS is making your bundles larger](https://web.dev/commonjs-larger-bundles). Angular CLI outputs warnings if it detects that your browser application depends on CommonJS modules. When you encounter a CommonJS dependency, consider asking the maintainer to support ECMAScript modules, contributing that support yourself, or using an alternative dependency which meets your needs. If the best option is to use a CommonJS dependency, you can disable these warnings by adding the CommonJS module name to `allowedCommonJsDependencies` option in the `build` options located in `angular.json`. "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "allowedCommonJsDependencies": [ "lodash" ] … } …}, ## [Configuring browser compatibility](https://angular.dev/#configuring-browser-compatibility) The Angular CLI uses [Browserslist](https://github.com/browserslist/browserslist) to ensure compatibility with different browser versions. Depending on supported browsers, Angular will automatically transform certain JavaScript and CSS features to ensure the built application does not use a feature which has not been implemented by a supported browser. However, the Angular CLI will not automatically add polyfills to supplement missing Web APIs. Use the `polyfills` option in `angular.json` to add polyfills. Internally, the Angular CLI uses the below default `browserslist` configuration which matches the [browsers that are supported](https://angular.dev/reference/versions#browser-support) by Angular. last 2 Chrome versionslast 1 Firefox versionlast 2 Edge major versionslast 2 Safari major versionslast 2 iOS major versionslast 2 Android major versionsFirefox ESR To override the internal configuration, run [`ng generate config browserslist`](https://angular.dev/cli/generate/config), which generates a `.browserslistrc` configuration file in the project directory. See the [browserslist repository](https://github.com/browserslist/browserslist) for more examples of how to target specific browsers and versions. Avoid expanding this list to more browsers. Even if your application code more broadly compatible, Angular itself might not be. You should only ever _reduce_ the set of browsers or versions in this list. **HELPFUL:** Use [browsersl.ist](https://browsersl.ist/) to display compatible browsers for a `browserslist` query. ## [Configuring Tailwind](https://angular.dev/#configuring-tailwind) Angular supports [Tailwind](https://tailwindcss.com/), a utility-first CSS framework. Follow the [Tailwind documentation](https://tailwindcss.com/docs/installation/framework-guides/angular) for integrating with Angular CLI. --- ## Page: https://angular.dev/tools/cli/serve You can serve your Angular CLI application with the `ng serve` command. This will compile your application, skip unnecessary optimizations, start a devserver, and automatically rebuild and live reload any subsequent changes. You can stop the server by pressing `Ctrl+C`. `ng serve` only executes the builder for the `serve` target in the default project as specified in `angular.json`. While any builder can be used here, the most common (and default) builder is `@angular-devkit/build-angular:dev-server`. You can determine which builder is being used for a particular project by looking up the `serve` target for that project. { "projects": { "my-app": { "architect": { // `ng serve` invokes the Architect target named `serve`. "serve": { "builder": "@angular-devkit/build-angular:dev-server", // ... }, "build": { /* ... */ } "test": { /* ... */ } } } }} This page discusses usage and options of `@angular-devkit/build-angular:dev-server`. ## [Proxying to a backend server](https://angular.dev/#proxying-to-a-backend-server) Use [proxying support](https://webpack.js.org/configuration/dev-server/#devserverproxy) to divert certain URLs to a backend server, by passing a file to the `--proxy-config` build option. For example, to divert all calls for `http://localhost:4200/api` to a server running on `http://localhost:3000/api`, take the following steps. 1. Create a file `proxy.conf.json` in your project's `src/` folder. 2. Add the following content to the new proxy file: { "/api": { "target": "http://localhost:3000", "secure": false } } 3. In the CLI configuration file, `angular.json`, add the `proxyConfig` option to the `serve` target: { "projects": { "my-app": { "architect": { "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "proxyConfig": "src/proxy.conf.json" } } } } } } 4. To run the development server with this proxy configuration, call `ng serve`. Edit the proxy configuration file to add configuration options; following are some examples. For a detailed description of all options, refer to the [webpack DevServer documentation](https://webpack.js.org/configuration/dev-server/#devserverproxy) when using `@angular-devkit/build-angular:browser`, or the [Vite DevServer documentation](https://vite.dev/config/server-options#server-proxy) when using `@angular-devkit/build-angular:browser-esbuild` or `@angular-devkit/build-angular:application`. **NOTE:** If you edit the proxy configuration file, you must relaunch the `ng serve` process to make your changes effective. ## [`localhost` resolution](https://angular.dev/#localhost-resolution) As of Node version 17, Node will _not_ always resolve `http://localhost:<port>` to `http://127.0.0.1:<port>` depending on each machine's configuration. If you get an `ECONNREFUSED` error using a proxy targeting a `localhost` URL, you can fix this issue by updating the target from `http://localhost:<port>` to `http://127.0.0.1:<port>`. See [the `http-proxy-middleware` documentation](https://github.com/chimurai/http-proxy-middleware#nodejs-17-econnrefused-issue-with-ipv6-and-localhost-705) for more information. --- ## Page: https://angular.dev/tools/cli/deployment When you are ready to deploy your Angular application to a remote server, you have various options. ## [Automatic deployment with the CLI](https://angular.dev/#automatic-deployment-with-the-cli) The Angular CLI command `ng deploy` executes the `deploy` [CLI builder](https://angular.dev/tools/cli/cli-builder) associated with your project. A number of third-party builders implement deployment capabilities to different platforms. You can add any of them to your project with `ng add`. When you add a package with deployment capability, it will automatically update your workspace configuration (`angular.json` file) with a `deploy` section for the selected project. You can then use the `ng deploy` command to deploy that project. For example, the following command automatically deploys a project to [Firebase](https://firebase.google.com/). ng add @angular/fireng deploy The command is interactive. In this case, you must have or create a Firebase account and authenticate using it. The command prompts you to select a Firebase project for deployment before building your application and uploading the production assets to Firebase. The table below lists tools which implement deployment functionality to different platforms. The `deploy` command for each package may require different command line options. You can read more by following the links associated with the package names below: | Deployment to | Setup Command | | --- | --- | | [Firebase hosting](https://firebase.google.com/docs/hosting) | [`ng add @angular/fire`](https://npmjs.org/package/@angular/fire) | | [Vercel](https://vercel.com/solutions/angular) | [`vercel init angular`](https://github.com/vercel/vercel/tree/main/examples/angular) | | [Netlify](https://www.netlify.com/) | [`ng add @netlify-builder/deploy`](https://npmjs.org/package/@netlify-builder/deploy) | | [GitHub pages](https://pages.github.com/) | [`ng add angular-cli-ghpages`](https://npmjs.org/package/angular-cli-ghpages) | | [Amazon Cloud S3](https://aws.amazon.com/s3/?nc2=h_ql_prod_st_s3) | [`ng add @jefiozie/ngx-aws-deploy`](https://www.npmjs.com/package/@jefiozie/ngx-aws-deploy) | If you're deploying to a self-managed server or there's no builder for your favorite cloud platform, you can either [create a builder](https://angular.dev/tools/cli/cli-builder) that allows you to use the `ng deploy` command, or read through this guide to learn how to manually deploy your application. ## [Manual deployment to a remote server](https://angular.dev/#manual-deployment-to-a-remote-server) To manually deploy your application, create a production build and copy the output directory to a web server or content delivery network (CDN). By default, `ng build` uses the `production` configuration. If you have customized your build configurations, you may want to confirm [production optimizations](https://angular.dev/tools/cli/deployment#production-optimizations) are being applied before deploying. `ng build` outputs the built artifacts to `dist/my-app/` by default, however this path can be configured with the `outputPath` option in the `@angular-devkit/build-angular:browser` builder. Copy this directory to the server and configure it to serve the directory. While this is a minimal deployment solution, there are a few requirements for the server to serve your Angular application correctly. ## [Server configuration](https://angular.dev/#server-configuration) This section covers changes you may need to configure on the server to run your Angular application. ### [Routed apps must fall back to `index.html`](https://angular.dev/#routed-apps-must-fall-back-to-indexhtml) Client-side rendered Angular applications are perfect candidates for serving with a static HTML server because all the content is static and generated at build time. If the application uses the Angular router, you must configure the server to return the application's host page (`index.html`) when asked for a file that it does not have. A routed application should support "deep links". A _deep link_ is a URL that specifies a path to a component inside the application. For example, `http://my-app.test/users/42` is a _deep link_ to the user detail page that displays the user with `id` 42. There is no issue when the user initially loads the index page and then navigates to that URL from within a running client. The Angular router performs the navigation _client-side_ and does not request a new HTML page. But clicking a deep link in an email, entering it in the browser address bar, or even refreshing the browser while already on the deep linked page will all be handled by the browser itself, _outside_ the running application. The browser makes a direct request to the server for `/users/42`, bypassing Angular's router. A static server routinely returns `index.html` when it receives a request for `http://my-app.test/`. But most servers by default will reject `http://my-app.test/users/42` and returns a `404 - Not Found` error _unless_ it is configured to return `index.html` instead. Configure the fallback route or 404 page to `index.html` for your server, so Angular is served for deep links and can display the correct route. Some servers call this fallback behavior "Single-Page Application" (SPA) mode. Once the browser loads the application, Angular router will read the URL to determine which page it is on and display `/users/42` correctly. For "real" 404 pages such as `http://my-app.test/does-not-exist`, the server does not require any additional configuration. [404 pages implemented in the Angular router](https://angular.dev/guide/routing/common-router-tasks#displaying-a-404-page) will be displayed correctly. ### [Requesting data from a different server (CORS)](https://angular.dev/#requesting-data-from-a-different-server-cors) Web developers may encounter a [_cross-origin resource sharing_](https://developer.mozilla.org/docs/Web/HTTP/CORS "Cross-origin") error when making a network request to a server other than the application's own host server. Browsers forbid such requests unless the server explicitly permits them. There isn't anything Angular or the client application can do about these errors. The _server_ must be configured to accept the application's requests. Read about how to enable CORS for specific servers at [enable-cors.org](https://enable-cors.org/server.html "Enabling"). ## [Production optimizations](https://angular.dev/#production-optimizations) `ng build` uses the `production` configuration unless configured otherwise. This configuration enables the following build optimization features. | Features | Details | | --- | --- | | [Ahead-of-Time (AOT) Compilation](https://angular.dev/tools/cli/aot-compiler) | Pre-compiles Angular component templates. | | [Production mode](https://angular.dev/tools/cli/deployment#development-only-features) | Optimizes the application for the best runtime performance | | Bundling | Concatenates your many application and library files into a minimum number of deployed files. | | Minification | Removes excess whitespace, comments, and optional tokens. | | Mangling | Renames functions, classes, and variables to use shorter, arbitrary identifiers. | | Dead code elimination | Removes unreferenced modules and unused code. | See [`ng build`](https://angular.dev/cli/build) for more about CLI build options and their effects. ### [Development-only features](https://angular.dev/#development-only-features) When you run an application locally using `ng serve`, Angular uses the development configuration at runtime which enables: * Extra safety checks such as [`expression-changed-after-checked`](https://angular.dev/errors/NG0100) detection. * More detailed error messages. * Additional debugging utilities such as the global `ng` variable with [debugging functions](https://angular.dev/api#core-global) and [Angular DevTools](https://angular.dev/tools/devtools) support. These features are helpful during development, but they require extra code in the app, which is undesirable in production. To ensure these features do not negatively impact bundle size for end users, Angular CLI removes development-only code from the bundle when building for production. Building your application with `ng build` by default uses the `production` configuration which removes these features from the output for optimal bundle size. ## [`--deploy-url`](https://angular.dev/#--deploy-url) `--deploy-url` is a command line option used to specify the base path for resolving relative URLs for assets such as images, scripts, and style sheets at _compile_ time. ng build --deploy-url /my/assets The effect and purpose of `--deploy-url` overlaps with [`<base href>`](https://angular.dev/guide/routing/common-router-tasks). Both can be used for initial scripts, stylesheets, lazy scripts, and css resources. Unlike `<base href>` which can be defined in a single place at runtime, the `--deploy-url` needs to be hard-coded into an application at build time. Prefer `<base href>` where possible. --- ## Page: https://angular.dev/tools/cli/end-to-end End-to-end or (E2E) testing is a form of testing used to assert your entire application works as expected from start to finish or _"end-to-end"_. E2E testing differs from unit testing in that it is completely decoupled from the underlying implementation details of your code. It is typically used to validate an application in a way that mimics the way a user would interact with it. This page serves as a guide to getting started with end-to-end testing in Angular using the Angular CLI. ## [Set Up E2E Testing](https://angular.dev/#set-up-e2e-testing) The Angular CLI downloads and installs everything you need to run end-to-end tests for your Angular application. ng e2e The `ng e2e` command will first check your project for the "e2e" target. If it can't locate it, the CLI will then prompt you which e2e package you would like to use and walk you through the setup. Cannot find "e2e" target for the specified project.You can add a package that implements these capabilities.For example:Cypress: ng add @cypress/schematicNightwatch: ng add @nightwatch/schematicsWebdriverIO: ng add @wdio/schematicsPlaywright: ng add playwright-ng-schematicsPuppeteer: ng add @puppeteer/ng-schematicsWould you like to add a package with "e2e" capabilities now?No❯ CypressNightwatchWebdriverIOPlaywrightPuppeteer If you don't find the test runner you would like to use from the list above, you can manually add a package using `ng add`. ## [Running E2E Tests](https://angular.dev/#running-e2e-tests) Now that your application is configured for end-to-end testing we can now run the same command to execute your tests. ng e2e Note, their isn't anything "special" about running your tests with any of the integrated e2e packages. The `ng e2e` command is really just running the `e2e` builder under the hood. You can always [create your own custom builder](https://angular.dev/tools/cli/cli-builder#creating-a-builder) named `e2e` and run it using `ng e2e`. ## [More information on end-to-end testing tools](https://angular.dev/#more-information-on-end-to-end-testing-tools) | Testing Tool | Details | | --- | --- | | Cypress | [Getting started with Cypress](https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test) | | Nightwatch | [Getting started with Nightwatch](https://nightwatchjs.org/guide/writing-tests/introduction.html) | | WebdriverIO | [Getting started with Webdriver.io](https://webdriver.io/docs/gettingstarted) | | Playwright | [Getting started with Playwright](https://playwright.dev/docs/writing-tests) | | Puppeteer | [Getting started with Puppeteer](https://pptr.dev/) | --- ## Page: https://angular.dev/tools/cli/build-system-migration In v17 and higher, the new build system provides an improved way to build Angular applications. This new build system includes: * A modern output format using ESM, with dynamic import expressions to support lazy module loading. * Faster build-time performance for both initial builds and incremental rebuilds. * Newer JavaScript ecosystem tools such as [esbuild](https://esbuild.github.io/) and [Vite](https://vitejs.dev/). * Integrated SSR and prerendering capabilities. * Automatic global and component stylesheet hot replacement. This new build system is stable and fully supported for use with Angular applications. You can migrate to the new build system with applications that use the `browser` builder. If using a custom builder, please refer to the documentation for that builder on possible migration options. **IMPORTANT:** The existing webpack-based build system is still considered stable and fully supported. Applications can continue to use the `browser` builder and projects can opt-out of migrating during an update. ## [For new applications](https://angular.dev/#for-new-applications) New applications will use this new build system by default via the `application` builder. ## [For existing applications](https://angular.dev/#for-existing-applications) Both automated and manual procedures are available depending on the requirements of the project. Starting with v18, the update process will ask if you would like to migrate existing applications to use the new build system via the automated migration. Prior to migrating, please consider reviewing the [Known Issues](https://angular.dev/#known-issues) section as it may contain relevant information for your project. **HELPFUL:** Remember to remove any CommonJS assumptions in the application server code if using SSR such as `require`, `__filename`, `__dirname`, or other constructs from the [CommonJS module scope](https://nodejs.org/api/modules.html#the-module-scope). All application code should be ESM compatible. This does not apply to third-party dependencies. ### [Automated migration (Recommended)](https://angular.dev/#automated-migration-recommended) The automated migration will adjust both the application configuration within `angular.json` as well as code and stylesheets to remove previous webpack-specific feature usage. While many changes can be automated and most applications will not require any further changes, each application is unique and there may be some manual changes required. After the migration, please attempt a build of the application as there could be new errors that will require adjustments within the code. The errors will attempt to provide solutions to the problem when possible and the later sections of this guide describe some of the more common situations that you may encounter. When updating to Angular v18 via `ng update`, you will be asked to execute the migration. This migration is entirely optional for v18 and can also be run manually at anytime after an update via the following command: ng update @angular/cli --name use-application-builder The migration does the following: * Converts existing `browser` or `browser-esbuild` target to `application` * Removes any previous SSR builders (because `application` does that now). * Updates configuration accordingly. * Merges `tsconfig.server.json` with `tsconfig.app.json` and adds the TypeScript option `"esModuleInterop": true` to ensure `express` imports are [ESM compliant](https://angular.dev/#esm-default-imports-vs-namespace-imports). * Updates application server code to use new bootstrapping and output directory structure. * Removes any webpack-specific builder stylesheet usage such as the tilde or caret in `@import`/`url()` and updates the configuration to provide equivalent behavior * Converts to use the new lower dependency `@angular/build` Node.js package if no other `@angular-devkit/build-angular` usage is found. ### [Manual migration](https://angular.dev/#manual-migration) Additionally for existing projects, you can manually opt-in to use the new builder on a per-application basis with two different options. Both options are considered stable and fully supported by the Angular team. The choice of which option to use is a factor of how many changes you will need to make to migrate and what new features you would like to use in the project. * The `browser-esbuild` builder builds only the client-side bundle of an application designed to be compatible with the existing `browser` builder that provides the preexisting build system. This builder provides equivalent build options, and in many cases, it serves as a drop-in replacement for existing `browser` applications. * The `application` builder covers an entire application, such as the client-side bundle, as well as optionally building a server for server-side rendering and performing build-time prerendering of static pages. The `application` builder is generally preferred as it improves server-side rendered (SSR) builds, and makes it easier for client-side rendered projects to adopt SSR in the future. However it requires a little more migration effort, particularly for existing SSR applications if performed manually. If the `application` builder is difficult for your project to adopt, `browser-esbuild` can be an easier solution which gives most of the build performance benefits with fewer breaking changes. #### [Manual migration to the compatibility builder](https://angular.dev/#manual-migration-to-the-compatibility-builder) A builder named `browser-esbuild` is available within the `@angular-devkit/build-angular` package that is present in an Angular CLI generated application. You can try out the new build system for applications that use the `browser` builder. If using a custom builder, please refer to the documentation for that builder on possible migration options. The compatibility option was implemented to minimize the amount of changes necessary to initially migrate your applications. This is provided via an alternate builder (`browser-esbuild`). You can update the `build` target for any application target to migrate to the new build system. The following is what you would typically find in `angular.json` for an application: ..."architect": { "build": { "builder": "@angular-devkit/build-angular:browser",... Changing the `builder` field is the only change you will need to make. ..."architect": { "build": { "builder": "@angular-devkit/build-angular:browser-esbuild",... #### [Manual migration to the new `application` builder](https://angular.dev/#manual-migration-to-the-new-application-builder) A builder named `application` is also available within the `@angular-devkit/build-angular` package that is present in an Angular CLI generated application. This builder is the default for all new applications created via `ng new`. The following is what you would typically find in `angular.json` for an application: ..."architect": { "build": { "builder": "@angular-devkit/build-angular:browser",... Changing the `builder` field is the first change you will need to make. ..."architect": { "build": { "builder": "@angular-devkit/build-angular:application",... Once the builder name has been changed, options within the `build` target will need to be updated. The following list discusses all the `browser` builder options that will need to be adjusted. * `main` should be renamed to `browser`. * `polyfills` should be an array, rather than a single file. * `buildOptimizer` should be removed, as this is covered by the `optimization` option. * `resourcesOutputPath` should be removed, this is now always `media`. * `vendorChunk` should be removed, as this was a performance optimization which is no longer needed. * `commonChunk` should be removed, as this was a performance optimization which is no longer needed. * `deployUrl` should be removed and is not supported. Prefer [`<base href>`](https://angular.dev/guide/routing/common-router-tasks) instead. See [deployment documentation](https://angular.dev/tools/cli/deployment#--deploy-url) for more information. * `ngswConfigPath` should be renamed to `serviceWorker`. If the application is not using SSR currently, this should be the final step to allow `ng build` to function. After executing `ng build` for the first time, there may be new warnings or errors based on behavioral differences or application usage of webpack-specific features. Many of the warnings will provide suggestions on how to remedy that problem. If it appears that a warning is incorrect or the solution is not apparent, please open an issue on [GitHub](https://github.com/angular/angular-cli/issues). Also, the later sections of this guide provide additional information on several specific cases as well as current known issues. For applications new to SSR, the [Angular SSR Guide](https://angular.dev/guide/ssr) provides additional information regarding the setup process for adding SSR to an application. For applications that are already using SSR, additional adjustments will be needed to update the application server to support the new integrated SSR capabilities. The `application` builder now provides the integrated functionality for all of the following preexisting builders: * `app-shell` * `prerender` * `server` * `ssr-dev-server` The `ng update` process will automatically remove usages of the `@nguniversal` scope packages where some of these builders were previously located. The new `@angular/ssr` package will also be automatically added and used with configuration and code being adjusted during the update. The `@angular/ssr` package supports the `browser` builder as well as the `application` builder. ## [Executing a build](https://angular.dev/#executing-a-build) Once you have updated the application configuration, builds can be performed using `ng build` as was previously done. Depending on the choice of builder migration, some of the command line options may be different. If the build command is contained in any `npm` or other scripts, ensure they are reviewed and updated. For applications that have migrated to the `application` builder and that use SSR and/or prererending, you also may be able to remove extra `ng run` commands from scripts now that `ng build` has integrated SSR support. ng build ## [Starting the development server](https://angular.dev/#starting-the-development-server) The development server will automatically detect the new build system and use it to build the application. To start the development server no changes are necessary to the `dev-server` builder configuration or command line. ng serve You can continue to use the [command line options](https://angular.dev/cli/serve) you have used in the past with the development server. **HELPFUL:** With the development server, you may see a small Flash of Unstyled Content (FOUC) on startup as the server initializes. The development server attempts to defer processing of stylesheets until first use to improve rebuild times. This will not occur in builds outside the development server. ### [Hot module replacement](https://angular.dev/#hot-module-replacement) Hot Module Replacement (HMR) is a technique used by development servers to avoid reloading the entire page when only part of an application is changed. The changes in many cases can be immediately shown in the browser which allows for an improved edit/refresh cycle while developing an application. While general JavaScript-based hot module replacement (HMR) is currently not supported, several more specific forms of HMR are available: * **global stylesheet** (`styles` build option) * **component stylesheet** (inline and file-based) * **component template** (inline and file-based) The HMR capabilities are automatically enabled and require no code or configuration changes to use. Angular provides HMR support for both file-based (`templateUrl`/`styleUrl`/`styleUrls`) and inline (`template`/`styles`) component styles and templates. The build system will attempt to compile and process the minimal amount of application code when it detects a stylesheet only change. If preferred, the HMR capabilities can be disabled by setting the `hmr` development server option to `false`. This can also be changed on the command line via: ng serve --no-hmr ### [Vite as a development server](https://angular.dev/#vite-as-a-development-server) The usage of Vite in the Angular CLI is currently within a _development server capacity only_. Even without using the underlying Vite build system, Vite provides a full-featured development server with client side support that has been bundled into a low dependency npm package. This makes it an ideal candidate to provide comprehensive development server functionality. The current development server process uses the new build system to generate a development build of the application in memory and passes the results to Vite to serve the application. The usage of Vite, much like the Webpack-based development server, is encapsulated within the Angular CLI `dev-server` builder and currently cannot be directly configured. ### [Prebundling](https://angular.dev/#prebundling) Prebundling provides improved build and rebuild times when using the development server. Vite provides [prebundling capabilities](https://vite.dev/guide/dep-pre-bundling) that are enabled by default when using the Angular CLI. The prebundling process analyzes all the third-party project dependencies within a project and processes them the first time the development server is executed. This process removes the need to rebuild and bundle the project's dependencies each time a rebuild occurs or the development server is executed. In most cases, no additional customization is required. However, some situations where it may be needed include: * Customizing loader behavior for imports within the dependency such as the [`loader` option](https://angular.dev/#file-extension-loader-customization) * Symlinking a dependency to local code for development such as [`npm link`](https://docs.npmjs.com/cli/v10/commands/npm-link) * Working around an error encountered during prebundling of a dependency The prebundling process can be fully disabled or individual dependencies can be excluded if needed by a project. The `dev-server` builder's `prebundle` option can be used for these customizations. To exclude specific dependencies, the `prebundle.exclude` option is available: "serve": { "builder": "@angular/build:dev-server", "options": { "prebundle": { "exclude": ["some-dep"] } }, By default, `prebundle` is set to `true` but can be set to `false` to fully disable prebundling. However, excluding specific dependencies is recommended instead since rebuild times will increase with prebundling disabled. "serve": { "builder": "@angular/build:dev-server", "options": { "prebundle": false }, ## [New features](https://angular.dev/#new-features) One of the main benefits of the application build system is the improved build and rebuild speed. However, the new application build system also provides additional features not present in the `browser` builder. **IMPORTANT:** The new features of the `application` builder described here are incompatible with the `karma` test builder by default because it is using the `browser` builder internally. Users can opt-in to use the `application` builder by setting the `builderMode` option to `application` for the `karma` builder. This option is currently in developer preview. If you notice any issues, please report them [here](https://github.com/angular/angular-cli/issues). ### [Build-time value replacement (define)](https://angular.dev/#build-time-value-replacement-define) The `define` option allows identifiers present in the code to be replaced with another value at build time. This is similar to the behavior of Webpack's `DefinePlugin` which was previously used with some custom Webpack configurations that used third-party builders. The option can either be used within the `angular.json` configuration file or on the command line. Configuring `define` within `angular.json` is useful for cases where the values are constant and able to be checked in to source control. Within the configuration file, the option is in the form of an object. The keys of the object represent the identifier to replace and the values of the object represent the corresponding replacement value for the identifier. An example is as follows: "build": { "builder": "@angular/build:application", "options": { ... "define": { "SOME_NUMBER": "5", "ANOTHER": "'this is a string literal, note the extra single quotes'", "REFERENCE": "globalThis.someValue.noteTheAbsentSingleQuotes" } } } **HELPFUL:** All replacement values are defined as strings within the configuration file. If the replacement is intended to be an actual string literal, it should be enclosed in single quote marks. This allows the flexibility of using any valid JSON type as well as a different identifier as a replacement. The command line usage is preferred for values that may change per build execution such as the git commit hash or an environment variable. The CLI will merge `--define` values from the command line with `define` values from `angular.json`, including both in a build. Command line usage takes precedence if the same identifier is present for both. For command line usage, the `--define` option uses the format of `IDENTIFIER=VALUE`. ng build --define SOME_NUMBER=5 --define "ANOTHER='these will overwrite existing'" Environment variables can also be selectively included in a build. For non-Windows shells, the quotes around the hash literal can be escaped directly if preferred. This example assumes a bash-like shell but similar behavior is available for other shells as well. export MY_APP_API_HOST="http://example.com"export API_RETRY=3ng build --define API_HOST=\'$MY_APP_API_HOST\' --define API_RETRY=$API_RETRY For either usage, TypeScript needs to be aware of the types for the identifiers to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with similar content: declare const SOME_NUMBER: number;declare const ANOTHER: string;declare const GIT_HASH: string;declare const API_HOST: string;declare const API_RETRY: number; The default project configuration is already setup to use any type definition files present in the project source directories. If the TypeScript configuration for the project has been altered, it may need to be adjusted to reference this newly added type definition file. **IMPORTANT:** This option will not replace identifiers contained within Angular metadata such as a Component or Directive decorator. ### [File extension loader customization](https://angular.dev/#file-extension-loader-customization) **IMPORTANT:** This feature is only available with the `application` builder. Some projects may need to control how all files with a specific file extension are loaded and bundled into an application. When using the `application` builder, the `loader` option can be used to handle these cases. The option allows a project to define the type of loader to use with a specified file extension. A file with the defined extension can then be used within the application code via an import statement or dynamic import expression. The available loaders that can be used are: * `text` - inlines the content as a `string` available as the default export * `binary` - inlines the content as a `Uint8Array` available as the default export * `file` - emits the file at the application output path and provides the runtime location of the file as the default export * `empty` - considers the content to be empty and will not include it in bundles The `empty` value, while less common, can be useful for compatibility of third-party libraries that may contain bundler-specific import usage that needs to be removed. One case for this is side-effect imports (`import 'my.css';`) of CSS files which has no effect in a browser. Instead, the project can use `empty` and then the CSS files can be added to the `styles` build option or use some other injection method. The loader option is an object-based option with the keys used to define the file extension and the values used to define the loader type. An example of the build option usage to inline the content of SVG files into the bundled application would be as follows: "build": { "builder": "@angular/build:application", "options": { ... "loader": { ".svg": "text" } } } An SVG file can then be imported: import contents from './some-file.svg';console.log(contents); // <svg>...</svg> Additionally, TypeScript needs to be aware of the module type for the import to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with the following or similar content: declare module "*.svg" { const content: string; export default content;} The default project configuration is already setup to use any type definition files (`.d.ts` files) present in the project source directories. If the TypeScript configuration for the project has been altered, the tsconfig may need to be adjusted to reference this newly added type definition file. ### [Import attribute loader customization](https://angular.dev/#import-attribute-loader-customization) For cases where only certain files should be loaded in a specific way, per file control over loading behavior is available. This is accomplished with a `loader` [import attribute](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with) that can be used with both import statements and expressions. The presence of the import attribute takes precedence over all other loading behavior including JS/TS and any `loader` build option values. For general loading for all files of an otherwise unsupported file type, the [`loader`](https://angular.dev/#file-extension-loader-customization) build option is recommended. For the import attribute, the following loader values are supported: * `text` - inlines the content as a `string` available as the default export * `binary` - inlines the content as a `Uint8Array` available as the default export * `file` - emits the file at the application output path and provides the runtime location of the file as the default export An additional requirement to use import attributes is that the TypeScript `module` option must be set to `esnext` to allow TypeScript to successfully build the application code. Once `ES2025` is available within TypeScript, this change will no longer be needed. At this time, TypeScript does not support type definitions that are based on import attribute values. The use of `@ts-expect-error`/`@ts-ignore` or the use of individual type definition files (assuming the file is only imported with the same loader attribute) is currently required. As an example, an SVG file can be imported as text via: // @ts-expect-error TypeScript cannot provide types based on attributes yetimport contents from './some-file.svg' with { loader: 'text' }; The same can be accomplished with an import expression inside an async function. async function loadSvg(): Promise<string> { // @ts-expect-error TypeScript cannot provide types based on attributes yet return import('./some-file.svg', { with: { loader: 'text' } }).then((m) => m.default);} For the import expression, the `loader` value must be a string literal to be statically analyzed. A warning will be issued if the value is not a string literal. The `file` loader is useful when a file will be loaded at runtime through either a `fetch()`, setting to an image elements `src`, or other similar method. // @ts-expect-error TypeScript cannot provide types based on attributes yetimport imagePath from './image.webp' with { loader: 'file' };console.log(imagePath); // media/image-ULK2SIIB.webp For production builds as shown in the code comment above, hashing will be automatically added to the path for long-term caching. **HELPFUL:** When using the development server and using a `loader` attribute to import a file from a Node.js package, that package must be excluded from prebundling via the development server `prebundle` option. ### [Import/export conditions](https://angular.dev/#import-export-conditions) Projects may need to map certain import paths to different files based on the type of build. This can be particularly useful for cases such as `ng serve` needing to use debug/development specific code but `ng build` needing to use code without any development features/information. Several import/export [conditions](https://nodejs.org/api/packages.html#community-conditions-definitions) are automatically applied to support these project needs: * For optimized builds, the `production` condition is enabled. * For non-optimized builds, the `development` condition is enabled. * For browser output code, the `browser` condition is enabled. An optimized build is determined by the value of the `optimization` option. When `optimization` is set to `true` or more specifically if `optimization.scripts` is set to `true`, then the build is considered optimized. This classification applies to both `ng build` and `ng serve`. In a new project, `ng build` defaults to optimized and `ng serve` defaults to non-optimized. A useful method to leverage these conditions within application code is to combine them with [subpath imports](https://nodejs.org/api/packages.html#subpath-imports). By using the following import statement: import {verboseLogging} from '#logger'; The file can be switched in the `imports` field in `package.json`: { ... "imports": { "#logger": { "development": "./src/logging/debug.ts", "default": "./src/logging/noop.ts" } }} For applications that are also using SSR, browser and server code can be switched by using the `browser` condition: { ... "imports": { "#crashReporter": { "browser": "./src/browser-logger.ts", "default": "./src/server-logger.ts" } }} These conditions also apply to Node.js packages and any defined [`exports`](https://nodejs.org/api/packages.html#conditional-exports) within the packages. **HELPFUL:** If currently using the `fileReplacements` build option, this feature may be able to replace its usage. ## [Known Issues](https://angular.dev/#known-issues) There are currently several known issues that you may encounter when trying the new build system. This list will be updated to stay current. If any of these issues are currently blocking you from trying out the new build system, please check back in the future as it may have been solved. ### [Type-checking of Web Worker code and processing of nested Web Workers](https://angular.dev/#type-checking-of-web-worker-code-and-processing-of-nested-web-workers) Web Workers can be used within application code using the same syntax (`new Worker(new URL('<workerfile>', import.meta.url))`) that is supported with the `browser` builder. However, the code within the Worker will not currently be type-checked by the TypeScript compiler. TypeScript code is supported just not type-checked. Additionally, any nested workers will not be processed by the build system. A nested worker is a Worker instantiation within another Worker file. ### [ESM default imports vs. namespace imports](https://angular.dev/#esm-default-imports-vs-namespace-imports) TypeScript by default allows default exports to be imported as namespace imports and then used in call expressions. This is unfortunately a divergence from the ECMAScript specification. The underlying bundler (`esbuild`) within the new build system expects ESM code that conforms to the specification. The build system will now generate a warning if your application uses an incorrect type of import of a package. However, to allow TypeScript to accept the correct usage, a TypeScript option must be enabled within the application's `tsconfig` file. When enabled, the [`esModuleInterop`](https://www.typescriptlang.org/tsconfig#esModuleInterop) option provides better alignment with the ECMAScript specification and is also recommended by the TypeScript team. Once enabled, you can update package imports where applicable to an ECMAScript conformant form. Using the [`moment`](https://npmjs.com/package/moment) package as an example, the following application code will cause runtime errors: import * as moment from 'moment';console.log(moment().format()); The build will generate a warning to notify you that there is a potential problem. The warning will be similar to: ▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace] src/main.ts:2:12: 2 │ console.log(moment().format()); ╵ ~~~~~~Consider changing "moment" to a default import instead: src/main.ts:1:7: 1 │ import * as moment from 'moment'; │ ~~~~~~~~~~~ ╵ moment However, you can avoid the runtime errors and the warning by enabling the `esModuleInterop` TypeScript option for the application and changing the import to the following: import moment from 'moment';console.log(moment().format()); ### [Order-dependent side-effectful imports in lazy modules](https://angular.dev/#order-dependent-side-effectful-imports-in-lazy-modules) Import statements that are dependent on a specific ordering and are also used in multiple lazy modules can cause top-level statements to be executed out of order. This is not common as it depends on the usage of side-effectful modules and does not apply to the `polyfills` option. This is caused by a [defect](https://github.com/evanw/esbuild/issues/399) in the underlying bundler but will be addressed in a future update. **IMPORTANT:** Avoiding the use of modules with non-local side effects (outside of polyfills) is recommended whenever possible regardless of the build system being used and avoids this particular issue. Modules with non-local side effects can have a negative effect on both application size and runtime performance as well. ## [Bug reports](https://angular.dev/#bug-reports) Report issues and feature requests on [GitHub](https://github.com/angular/angular-cli/issues). Please provide a minimal reproduction where possible to aid the team in addressing issues. --- ## Page: https://angular.dev/tools/cli/environments You can define different named build configurations for your project, such as `development` and `staging`, with different defaults. Each named configuration can have defaults for any of the options that apply to the various builder targets, such as `build`, `serve`, and `test`. The [Angular CLI](https://angular.dev/tools/cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment. ## [Angular CLI configurations](https://angular.dev/#angular-cli-configurations) Angular CLI builders support a `configurations` object, which allows overwriting specific options for a builder based on the configuration provided on the command line. { "projects": { "my-app": { "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { // By default, disable source map generation. "sourceMap": false }, "configurations": { // For the `debug` configuration, enable source maps. "debug": { "sourceMap": true } } }, … } } }} You can choose which configuration to use with the `--configuration` option. ng build --configuration debug Configurations can be applied to any Angular CLI builder. Multiple configurations can be specified with a comma separator. The configurations are applied in order, with conflicting options using the value from the last configuration. ng build --configuration debug,production,customer-facing ## [Configure environment-specific defaults](https://angular.dev/#configure-environment-specific-defaults) `@angular-devkit/build-angular:browser` supports file replacements, an option for substituting source files before executing a build. Using this in combination with `--configuration` provides a mechanism for configuring environment-specific data in your application. Start by [generating environments](https://angular.dev/cli/generate/environments) to create the `src/environments/` directory and configure the project to use file replacements. ng generate environments The project's `src/environments/` directory contains the base configuration file, `environment.ts`, which provides the default configuration for production. You can override default values for additional environments, such as `development` and `staging`, in target-specific configuration files. For example: my-app/src/environments├── environment.development.ts├── environment.staging.ts└── environment.ts The base file `environment.ts`, contains the default environment settings. For example: export const environment = { production: true}; The `build` command uses this as the build target when no environment is specified. You can add further variables, either as additional properties on the environment object, or as separate objects. For example, the following adds a default for a variable to the default environment: export const environment = { production: true, apiUrl: 'http://my-prod-url'}; You can add target-specific configuration files, such as `environment.development.ts`. The following content sets default values for the development build target: export const environment = { production: false, apiUrl: 'http://my-dev-url'}; ## [Using environment-specific variables in your app](https://angular.dev/#using-environment-specific-variables-in-your-app) To use the environment configurations you have defined, your components must import the original environments file: import { environment } from './environments/environment'; This ensures that the build and serve commands can find the configurations for specific build targets. The following code in the component file (`app.component.ts`) uses an environment variable defined in the configuration files. import { environment } from './../environments/environment';// Fetches from `http://my-prod-url` in production, `http://my-dev-url` in development.fetch(environment.apiUrl); The main CLI configuration file, `angular.json`, contains a `fileReplacements` section in the configuration for each build target, which lets you replace any file in the TypeScript program with a target-specific version of that file. This is useful for including target-specific code or variables in a build that targets a specific environment, such as production or staging. By default no files are replaced, however `ng generate environments` sets up this configuration automatically. You can change or add file replacements for specific build targets by editing the `angular.json` configuration directly. "configurations": { "development": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.development.ts" } ], … This means that when you build your development configuration with `ng build --configuration development`, the `src/environments/environment.ts` file is replaced with the target-specific version of the file, `src/environments/environment.development.ts`. To add a staging environment, create a copy of `src/environments/environment.ts` called `src/environments/environment.staging.ts`, then add a `staging` configuration to `angular.json`: "configurations": { "development": { … }, "production": { … }, "staging": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.staging.ts" } ] } } You can add more configuration options to this target environment as well. Any option that your build supports can be overridden in a build target configuration. To build using the staging configuration, run the following command: ng build --configuration staging By default, the `build` target includes `production` and `development` configurations and `ng serve` uses the development build of the application. You can also configure `ng serve` to use the targeted build configuration if you set the `buildTarget` option: "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { … }, "configurations": { "development": { // Use the `development` configuration of the `build` target. "buildTarget": "my-app:build:development" }, "production": { // Use the `production` configuration of the `build` target. "buildTarget": "my-app:build:production" } }, "defaultConfiguration": "development" }, The `defaultConfiguration` option specifies which configuration is used by default. When `defaultConfiguration` is not set, `options` are used directly without modification. --- ## Page: https://angular.dev/tools/cli/cli-builder A number of Angular CLI commands run a complex process on your code, such as building, testing, or serving your application. The commands use an internal tool called Architect to run _CLI builders_, which invoke another tool (bundler, test runner, server) to accomplish the desired task. Custom builders can perform an entirely new task, or to change which third-party tool is used by an existing command. This document explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder. **HELPFUL:** Find the code from the examples used here in this [GitHub repository](https://github.com/mgechev/cli-builders-demo). ## [CLI builders](https://angular.dev/#cli-builders) The internal Architect tool delegates work to handler functions called _builders_. A builder handler function receives two arguments: | Argument | Type | | --- | --- | | `options` | `JSONObject` | | `context` | `BuilderContext` | The separation of concerns here is the same as with [schematics](https://angular.dev/tools/cli/schematics-authoring), which are used for other CLI commands that touch your code (such as `ng generate`). * The `options` object is provided by the CLI user's options and configuration, while the `context` object is provided by the CLI Builder API automatically. * In addition to the contextual information, the `context` object also provides access to a scheduling method, `context.scheduleTarget()`. The scheduler executes the builder handler function with a given target configuration. The builder handler function can be synchronous (return a value), asynchronous (return a `Promise`), or watch and return multiple values (return an `Observable`). The return values must always be of type `BuilderOutput`. This object contains a Boolean `success` field and an optional `error` field that can contain an error message. Angular provides some builders that are used by the CLI for commands such as `ng build` and `ng test`. Default target configurations for these and other built-in CLI builders can be found and configured in the "architect" section of the [workspace configuration file](https://angular.dev/reference/configs/workspace-config), `angular.json`. Also, extend and customize Angular by creating your own builders, which you can run directly using the [`ng run` CLI command](https://angular.dev/cli/run). ### [Builder project structure](https://angular.dev/#builder-project-structure) A builder resides in a "project" folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior. For example, your `myBuilder` folder could contain the following files. | Files | Purpose | | --- | --- | | `src/my-builder.ts` | Main source file for the builder definition. | | `src/my-builder.spec.ts` | Source file for tests. | | `src/schema.json` | Definition of builder input options. | | `builders.json` | Builders definition. | | `package.json` | Dependencies. See [https://docs.npmjs.com/files/package.json](https://docs.npmjs.com/files/package.json). | | `tsconfig.json` | [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). | Builders can be published to `npm`, see [Publishing your Library](https://angular.dev/tools/libraries/creating-libraries). ## [Creating a builder](https://angular.dev/#creating-a-builder) As an example, create a builder that copies a file to a new location. To create a builder, use the `createBuilder()` CLI Builder function, and return a `Promise<BuilderOutput>` object. import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';import {JsonObject} from '@angular-devkit/core';import {promises as fs} from 'fs';interface Options extends JsonObject { source: string; destination: string;}export default createBuilder(copyFileBuilder);async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> { context.reportStatus(`Copying ${options.source} to ${options.destination}.`); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error('Failed to copy file.'); return { success: false, error: (err as Error).message, }; } context.reportStatus('Done.'); return {success: true};} Now let's add some logic to it. The following code retrieves the source and destination file paths from user options and copies the file from the source to the destination (using the [Promise version of the built-in NodeJS `copyFile()` function](https://nodejs.org/api/fs.html#fs_fspromises_copyfile_src_dest_mode)). If the copy operation fails, it returns an error with a message about the underlying problem. import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';import {JsonObject} from '@angular-devkit/core';import {promises as fs} from 'fs';interface Options extends JsonObject { source: string; destination: string;}export default createBuilder(copyFileBuilder);async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> { context.reportStatus(`Copying ${options.source} to ${options.destination}.`); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error('Failed to copy file.'); return { success: false, error: (err as Error).message, }; } context.reportStatus('Done.'); return {success: true};} ### [Handling output](https://angular.dev/#handling-output) By default, `copyFile()` does not print anything to the process standard output or error. If an error occurs, it might be difficult to understand exactly what the builder was trying to do when the problem occurred. Add some additional context by logging additional information using the `Logger` API. This also lets the builder itself be executed in a separate process, even if the standard output and error are deactivated. You can retrieve a `Logger` instance from the context. import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';import {JsonObject} from '@angular-devkit/core';import {promises as fs} from 'fs';interface Options extends JsonObject { source: string; destination: string;}export default createBuilder(copyFileBuilder);async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> { context.reportStatus(`Copying ${options.source} to ${options.destination}.`); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error('Failed to copy file.'); return { success: false, error: (err as Error).message, }; } context.reportStatus('Done.'); return {success: true};} ### [Progress and status reporting](https://angular.dev/#progress-and-status-reporting) The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces. To report progress, use the `context.reportProgress()` method, which takes a current value, optional total, and status string as arguments. The total can be any number. For example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far. The status string is unmodified unless you pass in a new string value. In our example, the copy operation either finishes or is still executing, so there's no need for a progress report, but you can report status so that a parent builder that called our builder would know what's going on. Use the `context.reportStatus()` method to generate a status string of any length. **HELPFUL:** There's no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it. Pass an empty string to remove the status. import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';import {JsonObject} from '@angular-devkit/core';import {promises as fs} from 'fs';interface Options extends JsonObject { source: string; destination: string;}export default createBuilder(copyFileBuilder);async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> { context.reportStatus(`Copying ${options.source} to ${options.destination}.`); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error('Failed to copy file.'); return { success: false, error: (err as Error).message, }; } context.reportStatus('Done.'); return {success: true};} ## [Builder input](https://angular.dev/#builder-input) You can invoke a builder indirectly through a CLI command such as `ng build`, or directly with the Angular CLI `ng run` command. In either case, you must provide required inputs, but can let other inputs default to values that are pre-configured for a specific _target_, specified by a [configuration](https://angular.dev/tools/cli/environments), or set on the command line. ### [Input validation](https://angular.dev/#input-validation) You define builder inputs in a JSON schema associated with that builder. Similar to schematics, the Architect tool collects the resolved input values into an `options` object, and validates their types against the schema before passing them to the builder function. For our example builder, `options` should be a `JsonObject` with two keys: a `source` and a `destination`, each of which are a string. You can provide the following schema for type validation of these values. { "$schema": "http://json-schema.org/schema", "type": "object", "properties": { "source": { "type": "string" }, "destination": { "type": "string" } }} **HELPFUL:** This is a minimal example, but the use of a schema for validation can be very powerful. For more information, see the [JSON schemas website](http://json-schema.org/). To link our builder implementation with its schema and name, you need to create a _builder definition_ file, which you can point to in `package.json`. Create a file named `builders.json` that looks like this: { "builders": { "copy": { "implementation": "./dist/my-builder.js", "schema": "./src/schema.json", "description": "Copies a file." } }} In the `package.json` file, add a `builders` key that tells the Architect tool where to find our builder definition file. { "name": "@example/copy-file", "version": "1.0.0", "description": "Builder for copying files", "builders": "builders.json", "dependencies": { "@angular-devkit/architect": "~0.1200.0", "@angular-devkit/core": "^12.0.0" }} The official name of our builder is now `@example/copy-file:copy`. The first part of this is the package name and the second part is the builder name as specified in the `builders.json` file. These values are accessed on `options.source` and `options.destination`. import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';import {JsonObject} from '@angular-devkit/core';import {promises as fs} from 'fs';interface Options extends JsonObject { source: string; destination: string;}export default createBuilder(copyFileBuilder);async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> { context.reportStatus(`Copying ${options.source} to ${options.destination}.`); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error('Failed to copy file.'); return { success: false, error: (err as Error).message, }; } context.reportStatus('Done.'); return {success: true};} ### [Target configuration](https://angular.dev/#target-configuration) A builder must have a defined target that associates it with a specific input configuration and project. Targets are defined in the `angular.json` [CLI configuration file](https://angular.dev/reference/configs/workspace-config). A target specifies the builder to use, its default options configuration, and named alternative configurations. Architect in the Angular CLI uses the target definition to resolve input options for a given run. The `angular.json` file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'serve'. By default, for example, the `ng build` command runs the builder `@angular-devkit/build-angular:browser` to perform the build task, and passes in default option values as specified for the `build` target in `angular.json`. …"myApp": { … "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/myApp", "index": "src/index.html", … }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", … } } }, … }}… The command passes the builder the set of default options specified in the "options" section. If you pass the `--configuration=production` flag, it uses the override values specified in the `production` configuration. Specify further option overrides individually on the command line. #### [Target strings](https://angular.dev/#target-strings) The generic `ng run` CLI command takes as its first argument a target string of the following form. project:target[:configuration] | | Details | | --- | --- | | project | The name of the Angular CLI project that the target is associated with. | | target | A named builder configuration from the `architect` section of the `angular.json` file. | | configuration | (optional) The name of a specific configuration override for the given target, as defined in the `angular.json` file. | If your builder calls another builder, it might need to read a passed target string. Parse this string into an object by using the `targetFromTargetString()` utility function from `@angular-devkit/architect`. ## [Schedule and run](https://angular.dev/#schedule-and-run) Architect runs builders asynchronously. To invoke a builder, you schedule a task to be run when all configuration resolution is complete. The builder function is not executed until the scheduler returns a `BuilderRun` control object. The CLI typically schedules tasks by calling the `context.scheduleTarget()` function, and then resolves input options using the target definition in the `angular.json` file. Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration, then further overwriting values from the overrides object passed to `context.scheduleTarget()`. For the Angular CLI, the overrides object is built from command line arguments. Architect validates the resulting options values against the schema of the builder. If inputs are valid, Architect creates the context and executes the builder. For more information see [Workspace Configuration](https://angular.dev/reference/configs/workspace-config). **HELPFUL:** You can also invoke a builder directly from another builder or test by calling `context.scheduleBuilder()`. You pass an `options` object directly to the method, and those option values are validated against the schema of the builder without further adjustment. Only the `context.scheduleTarget()` method resolves the configuration and overrides through the `angular.json` file. ### [Default architect configuration](https://angular.dev/#default-architect-configuration) Let's create a simple `angular.json` file that puts target configurations into context. You can publish the builder to npm (see [Publishing your Library](https://angular.dev/tools/libraries/creating-libraries#publishing-your-library)), and install it using the following command: npm install @example/copy-file If you create a new project with `ng new builder-test`, the generated `angular.json` file looks something like this, with only default builder configurations. { "projects": { "builder-test": { "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { // more options... "outputPath": "dist/builder-test", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json" }, "configurations": { "production": { // more options... "optimization": true, "aot": true, "buildOptimizer": true } } } } } }} ### [Adding a target](https://angular.dev/#adding-a-target) Add a new target that will run our builder to copy a file. This target tells the builder to copy the `package.json` file. * We will add a new target section to the `architect` object for our project * The target named `copy-package` uses our builder, which you published to `@example/copy-file`. * The options object provides default values for the two inputs that you defined. * `source` - The existing file you are copying. * `destination` - The path you want to copy to. { "projects": { "builder-test": { "architect": { "copy-package": { "builder": "@example/copy-file:copy", "options": { "source": "package.json", "destination": "package-copy.json" } }, // Existing targets... } } }} ### [Running the builder](https://angular.dev/#running-the-builder) To run our builder with the new target's default configuration, use the following CLI command. ng run builder-test:copy-package This copies the `package.json` file to `package-copy.json`. Use command-line arguments to override the configured defaults. For example, to run with a different `destination` value, use the following CLI command. ng run builder-test:copy-package --destination=package-other.json This copies the file to `package-other.json` instead of `package-copy.json`. Because you did not override the _source_ option, it will still copy from the default `package.json` file. ## [Testing a builder](https://angular.dev/#testing-a-builder) Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this [example](https://github.com/mgechev/cli-builders-demo). In the builder source directory, create a new test file `my-builder.spec.ts`. The test creates new instances of `JsonSchemaRegistry` (for schema validation), `TestingArchitectHost` (an in-memory implementation of `ArchitectHost`), and `Architect`. Here's an example of a test that runs the copy file builder. The test uses the builder to copy the `package.json` file and validates that the copied file's contents are the same as the source. import {Architect} from '@angular-devkit/architect';import {TestingArchitectHost} from '@angular-devkit/architect/testing';import {schema} from '@angular-devkit/core';import {promises as fs} from 'fs';import {join} from 'path';describe('Copy File Builder', () => { let architect: Architect; let architectHost: TestingArchitectHost; beforeEach(async () => { const registry = new schema.CoreSchemaRegistry(); registry.addPostTransform(schema.transforms.addUndefinedDefaults); // TestingArchitectHost() takes workspace and current directories. // Since we don't use those, both are the same in this case. architectHost = new TestingArchitectHost(__dirname, __dirname); architect = new Architect(architectHost, registry); // This will either take a Node package name, or a path to the directory // for the package.json file. await architectHost.addBuilderFromPackage(join(__dirname, '..')); }); it('can copy files', async () => { // A "run" can have multiple outputs, and contains progress information. const run = await architect.scheduleBuilder('@example/copy-file:copy', { source: 'package.json', destination: 'package-copy.json', }); // The "result" member (of type BuilderOutput) is the next output. const output = await run.result; // Stop the builder from running. This stops Architect from keeping // the builder-associated states in memory, since builders keep waiting // to be scheduled. await run.stop(); // Expect that the copied file is the same as its source. const sourceContent = await fs.readFile('package.json', 'utf8'); const destinationContent = await fs.readFile('package-copy.json', 'utf8'); expect(destinationContent).toBe(sourceContent); });}); **HELPFUL:** When running this test in your repo, you need the [`ts-node`](https://github.com/TypeStrong/ts-node) package. You can avoid this by renaming `my-builder.spec.ts` to `my-builder.spec.js`. ### [Watch mode](https://angular.dev/#watch-mode) Most builders to run once and return. However, this behavior is not entirely compatible with a builder that watches for changes (like a devserver, for example). Architect can support watch mode, but there are some things to look out for. * To be used with watch mode, a builder handler function should return an `Observable`. Architect subscribes to the `Observable` until it completes and might reuse it if the builder is scheduled again with the same arguments. * The builder should always emit a `BuilderOutput` object after each execution. Once it's been executed, it can enter a watch mode, to be triggered by an external event. If an event triggers it to restart, the builder should execute the `context.reportRunning()` function to tell Architect that it is running again. This prevents Architect from stopping the builder if another run is scheduled. When your builder calls `BuilderRun.stop()` to exit watch mode, Architect unsubscribes from the builder's `Observable` and calls the builder's teardown logic to clean up. This behavior also allows for long-running builds to be stopped and cleaned up. In general, if your builder is watching an external event, you should separate your run into three phases. | Phases | Details | | --- | --- | | Running | The task being performed, such as invoking a compiler. This ends when the compiler finishes and your builder emits a `BuilderOutput` object. | | Watching | Between two runs, watch an external event stream. For example, watch the file system for any changes. This ends when the compiler restarts, and `context.reportRunning()` is called. | | Completion | Either the task is fully completed, such as a compiler which needs to run a number of times, or the builder run was stopped (using `BuilderRun.stop()`). Architect executes teardown logic and unsubscribes from your builder's `Observable`. | ## [Summary](https://angular.dev/#summary) The CLI Builder API provides a means of changing the behavior of the Angular CLI by using builders to execute custom logic. * Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets. * Builders have option defaults specified in the `angular.json` configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags * The Angular team recommends that you use integration tests to test Architect builders. Use unit tests to validate the logic that the builder executes. * If your builder returns an `Observable`, it should clean up the builder in the teardown logic of that `Observable`. --- ## Page: https://angular.dev/tools/cli/schematics A schematic is a template-based code generator that supports complex logic. It is a set of instructions for transforming a software project by generating or modifying code. Schematics are packaged into collections and installed with npm. The schematic collection can be a powerful tool for creating, modifying, and maintaining any software project, but is particularly useful for customizing Angular projects to suit the particular needs of your own organization. You might use schematics, for example, to generate commonly-used UI patterns or specific components, using predefined templates or layouts. Use schematics to enforce architectural rules and conventions, making your projects consistent and interoperative. ## [Schematics for the Angular CLI](https://angular.dev/#schematics-for-the-angular-cli) Schematics are part of the Angular ecosystem. The Angular CLI uses schematics to apply transforms to a web-app project. You can modify these schematics, and define new ones to do things like update your code to fix breaking changes in a dependency, for example, or to add a new configuration option or framework to an existing project. Schematics that are included in the `@schematics/angular` collection are run by default by the commands `ng generate` and `ng add`. The package contains named schematics that configure the options that are available to the CLI for `ng generate` sub-commands, such as `ng generate component` and `ng generate service`. The sub-commands for `ng generate` are shorthand for the corresponding schematic. To specify and generate a particular schematic, or a collection of schematics, using the long form: ng generate my-schematic-collection:my-schematic-name or ng generate my-schematic-name --collection collection-name ### [Configuring CLI schematics](https://angular.dev/#configuring-cli-schematics) A JSON schema associated with a schematic tells the Angular CLI what options are available to commands and sub-commands, and determines the defaults. These defaults can be overridden by providing a different value for an option on the command line. See [Workspace Configuration](https://angular.dev/reference/configs/workspace-config) for information about how to change the generation option defaults for your workspace. The JSON schemas for the default schematics used by the CLI to generate projects and parts of projects are collected in the package [`@schematics/angular`](https://github.com/angular/angular-cli/tree/main/packages/schematics/angular). The schema describes the options available to the CLI for each of the `ng generate` sub-commands, as shown in the `--help` output. ## [Developing schematics for libraries](https://angular.dev/#developing-schematics-for-libraries) As a library developer, you can create your own collections of custom schematics to integrate your library with the Angular CLI. * An _add schematic_ lets developers install your library in an Angular workspace using `ng add` * _Generation schematics_ can tell the `ng generate` sub-commands how to modify projects, add configurations and scripts, and scaffold artifacts that are defined in your library * An _update schematic_ can tell the `ng update` command how to update your library's dependencies and adjust for breaking changes when you release a new version For more details of what these look like and how to create them, see: [Authoring Schematics](https://angular.dev/tools/cli/schematics-authoring) [Schematics for Libraries](https://angular.dev/tools/cli/schematics-for-libraries) ### [Add schematics](https://angular.dev/#add-schematics) An _add schematic_ is typically supplied with a library, so that the library can be added to an existing project with `ng add`. The `add` command uses your package manager to download new dependencies, and invokes an installation script that is implemented as a schematic. For example, the [`@angular/material`](https://material.angular.io/guide/schematics) schematic tells the `add` command to install and set up Angular Material and theming, and register new starter components that can be created with `ng generate`. Look at this one as an example and model for your own add schematic. Partner and third party libraries also support the Angular CLI with add schematics. For example, `@ng-bootstrap/schematics` adds [ng-bootstrap](https://ng-bootstrap.github.io/) to an app, and `@clr/angular` installs and sets up [Clarity from VMWare](https://clarity.design/documentation/get-started). An _add schematic_ can also update a project with configuration changes, add additional dependencies (such as polyfills), or scaffold package-specific initialization code. For example, the `@angular/pwa` schematic turns your application into a PWA by adding an application manifest and service worker. ### [Generation schematics](https://angular.dev/#generation-schematics) Generation schematics are instructions for the `ng generate` command. The documented sub-commands use the default Angular generation schematics, but you can specify a different schematic (in place of a sub-command) to generate an artifact defined in your library. Angular Material, for example, supplies generation schematics for the UI components that it defines. The following command uses one of these schematics to render an Angular Material `<mat-table>` that is pre-configured with a datasource for sorting and pagination. ng generate @angular/material:table <component-name> ### [Update schematics](https://angular.dev/#update-schematics) The `ng update` command can be used to update your workspace's library dependencies. If you supply no options or use the help option, the command examines your workspace and suggests libraries to update. ng updateWe analyzed your package.json, there are some packages to update: Name Version Command to update ‐------------------------------------------------------------------------------- @angular/cdk 7.2.2 -> 7.3.1 ng update @angular/cdk @angular/cli 7.2.3 -> 7.3.0 ng update @angular/cli @angular/core 7.2.2 -> 7.2.3 ng update @angular/core @angular/material 7.2.2 -> 7.3.1 ng update @angular/material rxjs 6.3.3 -> 6.4.0 ng update rxjs If you pass the command a set of libraries to update, it updates those libraries, their peer dependencies, and the peer dependencies that depend on them. **HELPFUL:** If there are inconsistencies (for example, if peer dependencies cannot be matched by a simple [semver](https://semver.io/) range), the command generates an error and does not change anything in the workspace. We recommend that you do not force an update of all dependencies by default. Try updating specific dependencies first. For more about how the `ng update` command works, see [Update Command](https://github.com/angular/angular-cli/blob/main/docs/specifications/update.md). If you create a new version of your library that introduces potential breaking changes, you can provide an _update schematic_ to enable the `ng update` command to automatically resolve any such changes in the project being updated. For example, suppose you want to update the Angular Material library. ng update @angular/material This command updates both `@angular/material` and its dependency `@angular/cdk` in your workspace's `package.json`. If either package contains an update schematic that covers migration from the existing version to a new version, the command runs that schematic on your workspace. --- ## Page: https://angular.dev/tools/cli/schematics-authoring You can create your own schematics to operate on Angular projects. Library developers typically package schematics with their libraries to integrate them with the Angular CLI. You can also create stand-alone schematics to manipulate the files and constructs in Angular applications as a way of customizing them for your development environment and making them conform to your standards and constraints. Schematics can be chained, running other schematics to perform complex operations. Manipulating the code in an application has the potential to be both very powerful and correspondingly dangerous. For example, creating a file that already exists would be an error, and if it was applied immediately, it would discard all the other changes applied so far. The Angular Schematics tooling guards against side effects and errors by creating a virtual file system. A schematic describes a pipeline of transformations that can be applied to the virtual file system. When a schematic runs, the transformations are recorded in memory, and only applied in the real file system once they're confirmed to be valid. ## [Schematics concepts](https://angular.dev/#schematics-concepts) The public API for schematics defines classes that represent the basic concepts. * The virtual file system is represented by a `Tree`. The `Tree` data structure contains a _base_ (a set of files that already exists) and a _staging area_ (a list of changes to be applied to the base). When making modifications, you don't actually change the base, but add those modifications to the staging area. * A `Rule` object defines a function that takes a `Tree`, applies transformations, and returns a new `Tree`. The main file for a schematic, `index.ts`, defines a set of rules that implement the schematic's logic. * A transformation is represented by an `Action`. There are four action types: `Create`, `Rename`, `Overwrite`, and `Delete`. * Each schematic runs in a context, represented by a `SchematicContext` object. The context object passed into a rule provides access to utility functions and metadata that the schematic might need to work with, including a logging API to help with debugging. The context also defines a _merge strategy_ that determines how changes are merged from the staged tree into the base tree. A change can be accepted or ignored, or throw an exception. ### [Defining rules and actions](https://angular.dev/#defining-rules-and-actions) When you create a new blank schematic with the [Schematics CLI](https://angular.dev/#schematics-cli), the generated entry function is a _rule factory_. A `RuleFactory` object defines a higher-order function that creates a `Rule`. import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';// You don't have to export the function as default.// You can also have more than one rule factory per file.export function helloWorld(_options: any): Rule { return (tree: Tree,_context: SchematicContext) => { return tree; };} Your rules can make changes to your projects by calling external tools and implementing logic. You need a rule, for example, to define how a template in the schematic is to be merged into the hosting project. Rules can make use of utilities provided with the `@schematics/angular` package. Look for helper functions for working with modules, dependencies, TypeScript, AST, JSON, Angular CLI workspaces and projects, and more. import { JsonAstObject, JsonObject, JsonValue, Path, normalize, parseJsonAst, strings,} from '@angular-devkit/core'; ### [Defining input options with a schema and interfaces](https://angular.dev/#defining-input-options-with-a-schema-and-interfaces) Rules can collect option values from the caller and inject them into templates. The options available to your rules, with their allowed values and defaults, are defined in the schematic's JSON schema file, `<schematic>/schema.json`. Define variable or enumerated data types for the schema using TypeScript interfaces. The schema defines the types and default values of variables used in the schematic. For example, the hypothetical "Hello World" schematic might have the following schema. { "properties": { "name": { "type": "string", "minLength": 1, "default": "world" }, "useColor": { "type": "boolean" } }} See examples of schema files for the Angular CLI command schematics in [`@schematics/angular`](https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/application/schema.json). ### [Schematic prompts](https://angular.dev/#schematic-prompts) Schematic _prompts_ introduce user interaction into schematic execution. Configure schematic options to display a customizable question to the user. The prompts are displayed before the execution of the schematic, which then uses the response as the value for the option. This lets users direct the operation of the schematic without requiring in-depth knowledge of the full spectrum of available options. The "Hello World" schematic might, for example, ask the user for their name, and display that name in place of the default name "world". To define such a prompt, add an `x-prompt` property to the schema for the `name` variable. Similarly, you can add a prompt to let the user decide whether the schematic uses color when executing its hello action. The schema with both prompts would be as follows. { "properties": { "name": { "type": "string", "minLength": 1, "default": "world", "x-prompt": "What is your name?" }, "useColor": { "type": "boolean", "x-prompt": "Would you like the response in color?" } }} #### [Prompt short-form syntax](https://angular.dev/#prompt-short-form-syntax) These examples use a shorthand form of the prompt syntax, supplying only the text of the question. In most cases, this is all that is required. Notice however, that the two prompts expect different types of input. When using the shorthand form, the most appropriate type is automatically selected based on the property's schema. In the example, the `name` prompt uses the `input` type because it is a string property. The `useColor` prompt uses a `confirmation` type because it is a Boolean property. In this case, "yes" corresponds to `true` and "no" corresponds to `false`. There are three supported input types. | Input type | Details | | --- | --- | | confirmation | A yes or no question; ideal for Boolean options. | | input | Textual input; ideal for string or number options. | | list | A predefined set of allowed values. | In the short form, the type is inferred from the property's type and constraints. | Property schema | Prompt type | | --- | --- | | "type": "boolean" | confirmation ("yes"=`true`, "no"=`false`) | | "type": "string" | input | | "type": "number" | input (only valid numbers accepted) | | "type": "integer" | input (only valid numbers accepted) | | "enum": \[…\] | list (enum members become list selections) | In the following example, the property takes an enumerated value, so the schematic automatically chooses the list type, and creates a menu from the possible values. "style": { "description": "The file extension or preprocessor to use for style files.", "type": "string", "default": "css", "enum": [ "css", "scss", "sass", "less", "styl" ], "x-prompt": "Which stylesheet format would you like to use?"} The prompt runtime automatically validates the provided response against the constraints provided in the JSON schema. If the value is not acceptable, the user is prompted for a new value. This ensures that any values passed to the schematic meet the expectations of the schematic's implementation, so that you do not need to add additional checks within the schematic's code. #### [Prompt long-form syntax](https://angular.dev/#prompt-long-form-syntax) The `x-prompt` field syntax supports a long form for cases where you require additional customization and control over the prompt. In this form, the `x-prompt` field value is a JSON object with subfields that customize the behavior of the prompt. | Field | Data value | | --- | --- | | type | `confirmation`, `input`, or `list` (selected automatically in short form) | | message | string (required) | | items | string and/or label/value object pair (only valid with type `list`) | The following example of the long form is from the JSON schema for the schematic that the CLI uses to [generate applications](https://github.com/angular/angular-cli/blob/ba8a6ea59983bb52a6f1e66d105c5a77517f062e/packages/schematics/angular/application/schema.json#L56). It defines the prompt that lets users choose which style preprocessor they want to use for the application being created. By using the long form, the schematic can provide more explicit formatting of the menu choices. "style": { "description": "The file extension or preprocessor to use for style files.", "type": "string", "default": "css", "enum": [ "css", "scss", "sass", "less" ], "x-prompt": { "message": "Which stylesheet format would you like to use?", "type": "list", "items": [ { "value": "css", "label": "CSS" }, { "value": "scss", "label": "SCSS [ https://sass-lang.com/documentation/syntax#scss ]" }, { "value": "sass", "label": "Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]" }, { "value": "less", "label": "Less [ https://lesscss.org/ ]" } ] },}, #### [x-prompt schema](https://angular.dev/#x-prompt-schema) The JSON schema that defines a schematic's options supports extensions to allow the declarative definition of prompts and their respective behavior. No additional logic or changes are required to the code of a schematic to support the prompts. The following JSON schema is a complete description of the long-form syntax for the `x-prompt` field. { "oneOf": [ { "type": "string" }, { "type": "object", "properties": { "type": { "type": "string" }, "message": { "type": "string" }, "items": { "type": "array", "items": { "oneOf": [ { "type": "string" }, { "type": "object", "properties": { "label": { "type": "string" }, "value": { } }, "required": [ "value" ] } ] } } }, "required": [ "message" ] } ]} ## [Schematics CLI](https://angular.dev/#schematics-cli) Schematics come with their own command-line tool. Using Node 6.9 or later, install the Schematics command line tool globally: npm install -g @angular-devkit/schematics-cli This installs the `schematics` executable, which you can use to create a new schematics collection in its own project folder, add a new schematic to an existing collection, or extend an existing schematic. In the following sections, you will create a new schematics collection using the CLI to introduce the files and file structure, and some of the basic concepts. The most common use of schematics, however, is to integrate an Angular library with the Angular CLI. Do this by creating the schematic files directly within the library project in an Angular workspace, without using the Schematics CLI. See [Schematics for Libraries](https://angular.dev/tools/cli/schematics-for-libraries). ### [Creating a schematics collection](https://angular.dev/#creating-a-schematics-collection) The following command creates a new schematic named `hello-world` in a new project folder of the same name. schematics blank --name=hello-world The `blank` schematic is provided by the Schematics CLI. The command creates a new project folder (the root folder for the collection) and an initial named schematic in the collection. Go to the collection folder, install your npm dependencies, and open your new collection in your favorite editor to see the generated files. For example, if you are using VS Code: cd hello-worldnpm installnpm run buildcode . The initial schematic gets the same name as the project folder, and is generated in `src/hello-world`. Add related schematics to this collection, and modify the generated skeleton code to define your schematic's functionality. Each schematic name must be unique within the collection. ### [Running a schematic](https://angular.dev/#running-a-schematic) Use the `schematics` command to run a named schematic. Provide the path to the project folder, the schematic name, and any mandatory options, in the following format. schematics <path-to-schematics-project>:<schematics-name> --<required-option>=<value> The path can be absolute or relative to the current working directory where the command is executed. For example, to run the schematic you just generated (which has no required options), use the following command. schematics .:hello-world ### [Adding a schematic to a collection](https://angular.dev/#adding-a-schematic-to-a-collection) To add a schematic to an existing collection, use the same command you use to start a new schematics project, but run the command inside the project folder. cd hello-worldschematics blank --name=goodbye-world The command generates the new named schematic inside your collection, with a main `index.ts` file and its associated test spec. It also adds the name, description, and factory function for the new schematic to the collection's schema in the `collection.json` file. ## [Collection contents](https://angular.dev/#collection-contents) The top level of the root project folder for a collection contains configuration files, a `node_modules` folder, and a `src/` folder. The `src/` folder contains subfolders for named schematics in the collection, and a schema, `collection.json`, which describes the collected schematics. Each schematic is created with a name, description, and factory function. { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "hello-world": { "description": "A blank schematic.", "factory": "./hello-world/index#helloWorld" } }} * The `$schema` property specifies the schema that the CLI uses for validation. * The `schematics` property lists named schematics that belong to this collection. Each schematic has a plain-text description, and points to the generated entry function in the main file. * The `factory` property points to the generated entry function. In this example, you invoke the `hello-world` schematic by calling the `helloWorld()` factory function. * The optional `schema` property points to a JSON schema file that defines the command-line options available to the schematic. * The optional `aliases` array specifies one or more strings that can be used to invoke the schematic. For example, the schematic for the Angular CLI "generate" command has an alias "g", that lets you use the command `ng g`. ### [Named schematics](https://angular.dev/#named-schematics) When you use the Schematics CLI to create a blank schematics project, the new blank schematic is the first member of the collection, and has the same name as the collection. When you add a new named schematic to this collection, it is automatically added to the `collection.json` schema. In addition to the name and description, each schematic has a `factory` property that identifies the schematic's entry point. In the example, you invoke the schematic's defined functionality by calling the `helloWorld()` function in the main file, `hello-world/index.ts`.  Each named schematic in the collection has the following main parts. | Parts | Details | | --- | --- | | `index.ts` | Code that defines the transformation logic for a named schematic. | | `schema.json` | Schematic variable definition. | | `schema.d.ts` | Schematic variables. | | `files/` | Optional component/template files to replicate. | It is possible for a schematic to provide all of its logic in the `index.ts` file, without additional templates. You can create dynamic schematics for Angular, however, by providing components and templates in the `files` folder, like those in standalone Angular projects. The logic in the index file configures these templates by defining rules that inject data and modify variables. --- ## Page: https://angular.dev/tools/cli/schematics-for-libraries When you create an Angular library, you can provide and package it with schematics that integrate it with the Angular CLI. With your schematics, your users can use `ng add` to install an initial version of your library, `ng generate` to create artifacts defined in your library, and `ng update` to adjust their project for a new version of your library that introduces breaking changes. All three types of schematics can be part of a collection that you package with your library. ## [Creating a schematics collection](https://angular.dev/#creating-a-schematics-collection) To start a collection, you need to create the schematic files. The following steps show you how to add initial support without modifying any project files. 1. In your library's root folder, create a `schematics` folder. 2. In the `schematics/` folder, create an `ng-add` folder for your first schematic. 3. At the root level of the `schematics` folder, create a `collection.json` file. 4. Edit the `collection.json` file to define the initial schema for your collection. { "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add my library to the project.", "factory": "./ng-add/index#ngAdd", "schema": "./ng-add/schema.json" } }} * The `$schema` path is relative to the Angular Devkit collection schema. * The `schematics` object describes the named schematics that are part of this collection. * The first entry is for a schematic named `ng-add`. It contains the description, and points to the factory function that is called when your schematic is executed. 5. In your library project's `package.json` file, add a "schematics" entry with the path to your schema file. The Angular CLI uses this entry to find named schematics in your collection when it runs commands. { "name": "my-lib", "version": "0.0.1", "scripts": { "build": "tsc -p tsconfig.schematics.json", "postbuild": "copyfiles schematics/*/schema.json schematics/*/files/** schematics/collection.json ../../dist/my-lib/" }, "peerDependencies": { "@angular/common": "^19.0.0", "@angular/core": "^19.0.0" }, "schematics": "./schematics/collection.json", "ng-add": { "save": "devDependencies" }, "devDependencies": { "copyfiles": "file:../../node_modules/copyfiles", "typescript": "file:../../node_modules/typescript" }} The initial schema that you have created tells the CLI where to find the schematic that supports the `ng add` command. Now you are ready to create that schematic. ## [Providing installation support](https://angular.dev/#providing-installation-support) A schematic for the `ng add` command can enhance the initial installation process for your users. The following steps define this type of schematic. 1. Go to the `<lib-root>/schematics/ng-add` folder. 2. Create the main file, `index.ts`. 3. Open `index.ts` and add the source code for your schematic factory function. import {Rule} from '@angular-devkit/schematics';import {addRootImport} from '@schematics/angular/utility';import {Schema} from './schema';export function ngAdd(options: Schema): Rule { // Add an import `MyLibModule` from `my-lib` to the root of the user's project. return addRootImport( options.project, ({code, external}) => code`${external('MyLibModule', 'my-lib')}`, );} The Angular CLI will install the latest version of the library automatically, and this example is taking it a step further by adding the `MyLibModule` to the root of the application. The `addRootImport` function accepts a callback that needs to return a code block. You can write any code inside of the string tagged with the `code` function and any external symbol have to be wrapped with the `external` function to ensure that the appropriate import statements are generated. ### [Define dependency type](https://angular.dev/#define-dependency-type) Use the `save` option of `ng-add` to configure if the library should be added to the `dependencies`, the `devDependencies`, or not saved at all in the project's `package.json` configuration file. { "name": "my-lib", "version": "0.0.1", "scripts": { "build": "tsc -p tsconfig.schematics.json", "postbuild": "copyfiles schematics/*/schema.json schematics/*/files/** schematics/collection.json ../../dist/my-lib/" }, "peerDependencies": { "@angular/common": "^19.0.0", "@angular/core": "^19.0.0" }, "schematics": "./schematics/collection.json", "ng-add": { "save": "devDependencies" }, "devDependencies": { "copyfiles": "file:../../node_modules/copyfiles", "typescript": "file:../../node_modules/typescript" }} Possible values are: | Values | Details | | --- | --- | | `false` | Don't add the package to `package.json` | | `true` | Add the package to the dependencies | | `"dependencies"` | Add the package to the dependencies | | `"devDependencies"` | Add the package to the devDependencies | ## [Building your schematics](https://angular.dev/#building-your-schematics) To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle. You must build your schematics _after_ you build your library, so they are placed in the correct directory. * Your library needs a custom Typescript configuration file with instructions on how to compile your schematics into your distributed library * To add the schematics to the library bundle, add scripts to the library's `package.json` file Assume you have a library project `my-lib` in your Angular workspace. To tell the library how to build the schematics, add a `tsconfig.schematics.json` file next to the generated `tsconfig.lib.json` file that configures the library build. 1. Edit the `tsconfig.schematics.json` file to add the following content. { "compilerOptions": { "baseUrl": ".", "lib": [ "es2018", "dom" ], "declaration": true, "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "noImplicitThis": true, "noUnusedParameters": true, "noUnusedLocals": true, "rootDir": "schematics", "outDir": "../../dist/my-lib/schematics", "skipDefaultLibCheck": true, "skipLibCheck": true, "sourceMap": true, "strictNullChecks": true, "target": "es6", "types": [ "jasmine", "node" ] }, "include": [ "schematics/**/*" ], "exclude": [ "schematics/*/files/**/*" ]} | Options | Details | | --- | --- | | `rootDir` | Specifies that your `schematics` folder contains the input files to be compiled. | | `outDir` | Maps to the library's output folder. By default, this is the `dist/my-lib` folder at the root of your workspace. | 2. To make sure your schematics source files get compiled into the library bundle, add the following scripts to the `package.json` file in your library project's root folder (`projects/my-lib`). { "name": "my-lib", "version": "0.0.1", "scripts": { "build": "tsc -p tsconfig.schematics.json", "postbuild": "copyfiles schematics/*/schema.json schematics/*/files/** schematics/collection.json ../../dist/my-lib/" }, "peerDependencies": { "@angular/common": "^19.0.0", "@angular/core": "^19.0.0" }, "schematics": "./schematics/collection.json", "ng-add": { "save": "devDependencies" }, "devDependencies": { "copyfiles": "file:../../node_modules/copyfiles", "typescript": "file:../../node_modules/typescript" }} * The `build` script compiles your schematic using the custom `tsconfig.schematics.json` file * The `postbuild` script copies the schematic files after the `build` script completes * Both the `build` and the `postbuild` scripts require the `copyfiles` and `typescript` dependencies. To install the dependencies, navigate to the path defined in `devDependencies` and run `npm install` before you run the scripts. ## [Providing generation support](https://angular.dev/#providing-generation-support) You can add a named schematic to your collection that lets your users use the `ng generate` command to create an artifact that is defined in your library. We'll assume that your library defines a service, `my-service`, that requires some setup. You want your users to be able to generate it using the following CLI command. ng generate my-lib:my-service To begin, create a new subfolder, `my-service`, in the `schematics` folder. ### [Configure the new schematic](https://angular.dev/#configure-the-new-schematic) When you add a schematic to the collection, you have to point to it in the collection's schema, and provide configuration files to define options that a user can pass to the command. 1. Edit the `schematics/collection.json` file to point to the new schematic subfolder, and include a pointer to a schema file that specifies inputs for the new schematic. { "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add my library to the project.", "factory": "./ng-add/index#ngAdd", "schema": "./ng-add/schema.json" }, "my-service": { "description": "Generate a service in the project.", "factory": "./my-service/index#myService", "schema": "./my-service/schema.json" } }} 2. Go to the `<lib-root>/schematics/my-service` folder. 3. Create a `schema.json` file and define the available options for the schematic. { "$schema": "http://json-schema.org/schema", "$id": "SchematicsMyService", "title": "My Service Schema", "type": "object", "properties": { "name": { "description": "The name of the service.", "type": "string" }, "path": { "type": "string", "format": "path", "description": "The path to create the service.", "visible": false, "$default": { "$source": "workingDirectory" } }, "project": { "type": "string", "description": "The name of the project.", "$default": { "$source": "projectName" } } }, "required": [ "name" ]} * _id_: A unique ID for the schema in the collection. * _title_: A human-readable description of the schema. * _type_: A descriptor for the type provided by the properties. * _properties_: An object that defines the available options for the schematic. Each option associates key with a type, description, and optional alias. The type defines the shape of the value you expect, and the description is displayed when the user requests usage help for your schematic. See the workspace schema for additional customizations for schematic options. 4. Create a `schema.ts` file and define an interface that stores the values of the options defined in the `schema.json` file. export interface Schema { // The name of the service. name: string; // The path to create the service. path?: string; // The name of the project. project?: string;} | Options | Details | | --- | --- | | name | The name you want to provide for the created service. | | path | Overrides the path provided to the schematic. The default path value is based on the current working directory. | | project | Provides a specific project to run the schematic on. In the schematic, you can provide a default if the option is not provided by the user. | ### [Add template files](https://angular.dev/#add-template-files) To add artifacts to a project, your schematic needs its own template files. Schematic templates support special syntax to execute code and variable substitution. 1. Create a `files/` folder inside the `schematics/my-service/` folder. 2. Create a file named `__name@dasherize__.service.ts.template` that defines a template to use for generating files. This template will generate a service that already has Angular's `HttpClient` injected into an `http` property. import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class <%= classify(name) %>Service { private http = inject(HttpClient); } * The `classify` and `dasherize` methods are utility functions that your schematic uses to transform your source template and filename. * The `name` is provided as a property from your factory function. It is the same `name` you defined in the schema. ### [Add the factory function](https://angular.dev/#add-the-factory-function) Now that you have the infrastructure in place, you can define the main function that performs the modifications you need in the user's project. The Schematics framework provides a file templating system, which supports both path and content templates. The system operates on placeholders defined inside files or paths that loaded in the input `Tree`. It fills these in using values passed into the `Rule`. For details of these data structures and syntax, see the [Schematics README](https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/schematics/README.md). 1. Create the main file `index.ts` and add the source code for your schematic factory function. 2. First, import the schematics definitions you will need. The Schematics framework offers many utility functions to create and use rules when running a schematic. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} 3. Import the defined schema interface that provides the type information for your schematic's options. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} 4. To build up the generation schematic, start with an empty rule factory. import {Rule, Tree} from '@angular-devkit/schematics';import {Schema as MyServiceSchema} from './schema';export function myService(options: MyServiceSchema): Rule { return (tree: Tree) => tree;} This rule factory returns the tree without modification. The options are the option values passed through from the `ng generate` command. ## [Define a generation rule](https://angular.dev/#define-a-generation-rule) You now have the framework in place for creating the code that actually modifies the user's application to set it up for the service defined in your library. The Angular workspace where the user installed your library contains multiple projects (applications and libraries). The user can specify the project on the command line, or let it default. In either case, your code needs to identify the specific project to which this schematic is being applied, so that you can retrieve information from the project configuration. Do this using the `Tree` object that is passed in to the factory function. The `Tree` methods give you access to the complete file tree in your workspace, letting you read and write files during the execution of the schematic. ### [Get the project configuration](https://angular.dev/#get-the-project-configuration) 1. To determine the destination project, use the `workspaces.readWorkspace` method to read the contents of the workspace configuration file, `angular.json`. To use `workspaces.readWorkspace` you need to create a `workspaces.WorkspaceHost` from the `Tree`. Add the following code to your factory function. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} Be sure to check that the context exists and throw the appropriate error. 2. Now that you have the project name, use it to retrieve the project-specific configuration information. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} The `workspace.projects` object contains all the project-specific configuration information. 3. The `options.path` determines where the schematic template files are moved to once the schematic is applied. The `path` option in the schematic's schema is substituted by default with the current working directory. If the `path` is not defined, use the `sourceRoot` from the project configuration along with the `projectType`. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} ### [Define the rule](https://angular.dev/#define-the-rule) A `Rule` can use external template files, transform them, and return another `Rule` object with the transformed template. Use the templating to generate any custom files required for your schematic. 1. Add the following code to your factory function. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} | Methods | Details | | --- | --- | | `apply()` | Applies multiple rules to a source and returns the transformed source. It takes 2 arguments, a source and an array of rules. | | `url()` | Reads source files from your filesystem, relative to the schematic. | | `applyTemplates()` | Receives an argument of methods and properties you want make available to the schematic template and the schematic filenames. It returns a `Rule`. This is where you define the `classify()` and `dasherize()` methods, and the `name` property. | | `classify()` | Takes a value and returns the value in title case. For example, if the provided name is `my service`, it is returned as `MyService`. | | `dasherize()` | Takes a value and returns the value in dashed and lowercase. For example, if the provided name is MyService, it is returned as `my-service`. | | `move()` | Moves the provided source files to their destination when the schematic is applied. | 2. Finally, the rule factory must return a rule. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} The `chain()` method lets you combine multiple rules into a single rule, so that you can perform multiple operations in a single schematic. Here you are only merging the template rules with any code executed by the schematic. See a complete example of the following schematic rule function. import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith,} from '@angular-devkit/schematics';import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';import {Schema as MyServiceSchema} from './schema';function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, };}export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; } const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]); return chain([mergeWith(templateSource)]); };} For more information about rules and utility methods, see [Provided Rules](https://github.com/angular/angular-cli/tree/main/packages/angular_devkit/schematics#provided-rules). ## [Running your library schematic](https://angular.dev/#running-your-library-schematic) After you build your library and schematics, you can install the schematics collection to run against your project. The following steps show you how to generate a service using the schematic you created earlier. ### [Build your library and schematics](https://angular.dev/#build-your-library-and-schematics) From the root of your workspace, run the `ng build` command for your library. ng build my-lib Then, you change into your library directory to build the schematic cd projects/my-libnpm run build ### [Link the library](https://angular.dev/#link-the-library) Your library and schematics are packaged and placed in the `dist/my-lib` folder at the root of your workspace. For running the schematic, you need to link the library into your `node_modules` folder. From the root of your workspace, run the `npm link` command with the path to your distributable library. npm link dist/my-lib ### [Run the schematic](https://angular.dev/#run-the-schematic) Now that your library is installed, run the schematic using the `ng generate` command. ng generate my-lib:my-service --name my-data In the console, you see that the schematic was run and the `my-data.service.ts` file was created in your application folder. CREATE src/app/my-data.service.ts (208 bytes) --- ## Page: https://angular.dev/tools/cli/template-typecheck ## [Overview of template type checking](https://angular.dev/#overview-of-template-type-checking) Just as TypeScript catches type errors in your code, Angular checks the expressions and bindings within the templates of your application and can report any type errors it finds. Angular currently has three modes of doing this, depending on the value of the `fullTemplateTypeCheck` and `strictTemplates` flags in [Angular's compiler options](https://angular.dev/reference/configs/angular-compiler-options). ### [Basic mode](https://angular.dev/#basic-mode) In the most basic type-checking mode, with the `fullTemplateTypeCheck` flag set to `false`, Angular validates only top-level expressions in a template. If you write `<map [city]="user.address.city">`, the compiler verifies the following: * `user` is a property on the component class * `user` is an object with an address property * `user.address` is an object with a city property The compiler does not verify that the value of `user.address.city` is assignable to the city input of the `<map>` component. The compiler also has some major limitations in this mode: * Importantly, it doesn't check embedded views, such as `*ngIf`, `*ngFor`, other `<ng-template>` embedded view. * It doesn't figure out the types of `#refs`, the results of pipes, or the type of `$event` in event bindings. In many cases, these things end up as type `any`, which can cause subsequent parts of the expression to go unchecked. ### [Full mode](https://angular.dev/#full-mode) If the `fullTemplateTypeCheck` flag is set to `true`, Angular is more aggressive in its type-checking within templates. In particular: * Embedded views (such as those within an `*ngIf` or `*ngFor`) are checked * Pipes have the correct return type * Local references to directives and pipes have the correct type (except for any generic parameters, which will be `any`) The following still have type `any`. * Local references to DOM elements * The `$event` object * Safe navigation expressions **IMPORTANT:** The `fullTemplateTypeCheck` flag has been deprecated in Angular 13. The `strictTemplates` family of compiler options should be used instead. ### [Strict mode](https://angular.dev/#strict-mode) Angular maintains the behavior of the `fullTemplateTypeCheck` flag, and introduces a third "strict mode". Strict mode is a superset of full mode, and is accessed by setting the `strictTemplates` flag to true. This flag supersedes the `fullTemplateTypeCheck` flag. In addition to the full mode behavior, Angular does the following: * Verifies that component/directive bindings are assignable to their `@Input()`s * Obeys TypeScript's `strictNullChecks` flag when validating the preceding mode * Infers the correct type of components/directives, including generics * Infers template context types where configured (for example, allowing correct type-checking of `NgFor`) * Infers the correct type of `$event` in component/directive, DOM, and animation event bindings * Infers the correct type of local references to DOM elements, based on the tag name (for example, the type that `document.createElement` would return for that tag) ## [Checking of `*ngFor`](https://angular.dev/#checking-of-ngfor) The three modes of type-checking treat embedded views differently. Consider the following example. interface User { name: string; address: { city: string; state: string; }} <div *ngFor="let user of users"> <h2>{{config.title}}</h2> <span>City: {{user.address.city}}</span></div> The `<h2>` and the `<span>` are in the `*ngFor` embedded view. In basic mode, Angular doesn't check either of them. However, in full mode, Angular checks that `config` and `user` exist and assumes a type of `any`. In strict mode, Angular knows that the `user` in the `<span>` has a type of `User`, and that `address` is an object with a `city` property of type `string`. ## [Troubleshooting template errors](https://angular.dev/#troubleshooting-template-errors) With strict mode, you might encounter template errors that didn't arise in either of the previous modes. These errors often represent genuine type mismatches in the templates that were not caught by the previous tooling. If this is the case, the error message should make it clear where in the template the problem occurs. There can also be false positives when the typings of an Angular library are either incomplete or incorrect, or when the typings don't quite line up with expectations as in the following cases. * When a library's typings are wrong or incomplete (for example, missing `null | undefined` if the library was not written with `strictNullChecks` in mind) * When a library's input types are too narrow and the library hasn't added appropriate metadata for Angular to figure this out. This usually occurs with disabled or other common Boolean inputs used as attributes, for example, `<input disabled>`. * When using `$event.target` for DOM events (because of the possibility of event bubbling, `$event.target` in the DOM typings doesn't have the type you might expect) In case of a false positive like these, there are a few options: * Use the `$any()` type-cast function in certain contexts to opt out of type-checking for a part of the expression * Disable strict checks entirely by setting `strictTemplates: false` in the application's TypeScript configuration file, `tsconfig.json` * Disable certain type-checking operations individually, while maintaining strictness in other aspects, by setting a _strictness flag_ to `false` * If you want to use `strictTemplates` and `strictNullChecks` together, opt out of strict null type checking specifically for input bindings using `strictNullInputTypes` Unless otherwise commented, each following option is set to the value for `strictTemplates` (`true` when `strictTemplates` is `true` and conversely, the other way around). | Strictness flag | Effect | | --- | --- | | `strictInputTypes` | Whether the assignability of a binding expression to the `@Input()` field is checked. Also affects the inference of directive generic types. | | `strictInputAccessModifiers` | Whether access modifiers such as `private`/`protected`/`readonly` are honored when assigning a binding expression to an `@Input()`. If disabled, the access modifiers of the `@Input` are ignored; only the type is checked. This option is `false` by default, even with `strictTemplates` set to `true`. | | `strictNullInputTypes` | Whether `strictNullChecks` is honored when checking `@Input()` bindings (per `strictInputTypes`). Turning this off can be useful when using a library that was not built with `strictNullChecks` in mind. | | `strictAttributeTypes` | Whether to check `@Input()` bindings that are made using text attributes. For example, `<input matInput disabled="true">` (setting the `disabled` property to the string `'true'`) vs `<input matInput [disabled]="true">` (setting the `disabled` property to the boolean `true`). | | `strictSafeNavigationTypes` | Whether the return type of safe navigation operations (for example, `user?.name` will be correctly inferred based on the type of `user`). If disabled, `user?.name` will be of type `any`. | | `strictDomLocalRefTypes` | Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for `<input #ref>`. | | `strictOutputEventTypes` | Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`. | | `strictDomEventTypes` | Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`. | | `strictContextGenerics` | Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be `any`. | | `strictLiteralTypes` | Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be `any`. This flag is `true` when _either_ `fullTemplateTypeCheck` or `strictTemplates` is set to `true`. | If you still have issues after troubleshooting with these flags, fall back to full mode by disabling `strictTemplates`. If that doesn't work, an option of last resort is to turn off full mode entirely with `fullTemplateTypeCheck: false`. A type-checking error that you cannot resolve with any of the recommended methods can be the result of a bug in the template type-checker itself. If you get errors that require falling back to basic mode, it is likely to be such a bug. If this happens, [file an issue](https://github.com/angular/angular/issues) so the team can address it. ## [Inputs and type-checking](https://angular.dev/#inputs-and-type-checking) The template type checker checks whether a binding expression's type is compatible with that of the corresponding directive input. As an example, consider the following component: export interface User { name: string;}@Component({ selector: 'user-detail', template: '{{ user.name }}',})export class UserDetailComponent { @Input() user: User;} The `AppComponent` template uses this component as follows: @Component({ selector: 'app-root', template: '<user-detail [user]="selectedUser"></user-detail>',})export class AppComponent { selectedUser: User | null = null;} Here, during type checking of the template for `AppComponent`, the `[user]="selectedUser"` binding corresponds with the `UserDetailComponent.user` input. Therefore, Angular assigns the `selectedUser` property to `UserDetailComponent.user`, which would result in an error if their types were incompatible. TypeScript checks the assignment according to its type system, obeying flags such as `strictNullChecks` as they are configured in the application. Avoid run-time type errors by providing more specific in-template type requirements to the template type checker. Make the input type requirements for your own directives as specific as possible by providing template-guard functions in the directive definition. See [Improving template type checking for custom directives](https://angular.dev/guide/directives/structural-directives#directive-type-checks) in this guide. ### [Strict null checks](https://angular.dev/#strict-null-checks) When you enable `strictTemplates` and the TypeScript flag `strictNullChecks`, typecheck errors might occur for certain situations that might not easily be avoided. For example: * A nullable value that is bound to a directive from a library which did not have `strictNullChecks` enabled. For a library compiled without `strictNullChecks`, its declaration files will not indicate whether a field can be `null` or not. For situations where the library handles `null` correctly, this is problematic, as the compiler will check a nullable value against the declaration files which omit the `null` type. As such, the compiler produces a type-check error because it adheres to `strictNullChecks`. * Using the `async` pipe with an Observable which you know will emit synchronously. The `async` pipe currently assumes that the Observable it subscribes to can be asynchronous, which means that it's possible that there is no value available yet. In that case, it still has to return something —which is `null`. In other words, the return type of the `async` pipe includes `null`, which might result in errors in situations where the Observable is known to emit a non-nullable value synchronously. There are two potential workarounds to the preceding issues: * In the template, include the non-null assertion operator `!` at the end of a nullable expression, such as <user-detail [user]="user!"></user-detail> In this example, the compiler disregards type incompatibilities in nullability, just as in TypeScript code. In the case of the `async` pipe, notice that the expression needs to be wrapped in parentheses, as in <user-detail [user]="(user$ | async)!"></user-detail> * Disable strict null checks in Angular templates completely. When `strictTemplates` is enabled, it is still possible to disable certain aspects of type checking. Setting the option `strictNullInputTypes` to `false` disables strict null checks within Angular templates. This flag applies for all components that are part of the application. As a library author, you can take several measures to provide an optimal experience for your users. First, enabling `strictNullChecks` and including `null` in an input's type, as appropriate, communicates to your consumers whether they can provide a nullable value or not. Additionally, it is possible to provide type hints that are specific to the template type checker. See [Improving template type checking for custom directives](https://angular.dev/guide/directives/structural-directives#directive-type-checks), and [Input setter coercion](https://angular.dev/#input-setter-coercion). ## [Input setter coercion](https://angular.dev/#input-setter-coercion) Occasionally it is desirable for the `@Input()` of a directive or component to alter the value bound to it, typically using a getter/setter pair for the input. As an example, consider this custom button component: Consider the following directive: @Component({ selector: 'submit-button', template: ` <div class="wrapper"> <button [disabled]="disabled">Submit</button> </div> `,})class SubmitButton { private _disabled: boolean; @Input() get disabled(): boolean { return this._disabled; } set disabled(value: boolean) { this._disabled = value; }} Here, the `disabled` input of the component is being passed on to the `<button>` in the template. All of this works as expected, as long as a `boolean` value is bound to the input. But, suppose a consumer uses this input in the template as an attribute: <submit-button disabled></submit-button> This has the same effect as the binding: <submit-button [disabled]="''"></submit-button> At runtime, the input will be set to the empty string, which is not a `boolean` value. Angular component libraries that deal with this problem often "coerce" the value into the right type in the setter: set disabled(value: boolean) { this._disabled = (value === '') || value;} It would be ideal to change the type of `value` here, from `boolean` to `boolean|''`, to match the set of values which are actually accepted by the setter. TypeScript prior to version 4.3 requires that both the getter and setter have the same type, so if the getter should return a `boolean` then the setter is stuck with the narrower type. If the consumer has Angular's strictest type checking for templates enabled, this creates a problem: the empty string (`''`) is not actually assignable to the `disabled` field, which creates a type error when the attribute form is used. As a workaround for this problem, Angular supports checking a wider, more permissive type for `@Input()` than is declared for the input field itself. Enable this by adding a static property with the `ngAcceptInputType_` prefix to the component class: class SubmitButton { private _disabled: boolean; @Input() get disabled(): boolean { return this._disabled; } set disabled(value: boolean) { this._disabled = (value === '') || value; } static ngAcceptInputType_disabled: boolean|'';} Since TypeScript 4.3, the setter could have been declared to accept `boolean|''` as type, making the input setter coercion field obsolete. As such, input setters coercion fields have been deprecated. This field does not need to have a value. Its existence communicates to the Angular type checker that the `disabled` input should be considered as accepting bindings that match the type `boolean|''`. The suffix should be the `@Input` _field_ name. Care should be taken that if an `ngAcceptInputType_` override is present for a given input, then the setter should be able to handle any values of the overridden type. ## [Disabling type checking using `$any()`](https://angular.dev/#disabling-type-checking-using-any) Disable checking of a binding expression by surrounding the expression in a call to the `$any()` cast pseudo-function. The compiler treats it as a cast to the `any` type just like in TypeScript when a `<any>` or `as any` cast is used. In the following example, casting `person` to the `any` type suppresses the error `Property address does not exist`. @Component({ selector: 'my-component', template: '{{$any(person).address.street}}'})class MyComponent { person?: Person;} --- ## Page: https://angular.dev/tools/cli/aot-compiler An Angular application consists mainly of components and their HTML templates. Because the components and templates provided by Angular cannot be understood by the browser directly, Angular applications require a compilation process before they can run in a browser. The Angular ahead-of-time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase _before_ the browser downloads and runs that code. Compiling your application during the build process provides a faster rendering in the browser. This guide explains how to specify metadata and apply available compiler options to compile your applications efficiently using the AOT compiler. Here are some reasons you might want to use AOT. | Reasons | Details | | --- | --- | | Faster rendering | With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the application first. | | Fewer asynchronous requests | The compiler _inlines_ external HTML templates and CSS style sheets within the application JavaScript, eliminating separate ajax requests for those source files. | | Smaller Angular framework download size | There's no need to download the Angular compiler if the application is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload. | | Detect template errors earlier | The AOT compiler detects and reports template binding errors during the build step before users can see them. | | Better security | AOT compiles HTML templates and components into JavaScript files long before they are served to the client. With no templates to read and no risky client-side HTML or JavaScript evaluation, there are fewer opportunities for injection attacks. | ## [Choosing a compiler](https://angular.dev/#choosing-a-compiler) Angular offers two ways to compile your application: | Angular compile | Details | | --- | --- | | Just-in-Time (JIT) | Compiles your application in the browser at runtime. This was the default until Angular 8. | | Ahead-of-Time (AOT) | Compiles your application and libraries at build time. This is the default starting in Angular 9. | When you run the [`ng build`](https://angular.dev/cli/build) (build only) or [`ng serve`](https://angular.dev/cli/serve) (build and serve locally) CLI commands, the type of compilation (JIT or AOT) depends on the value of the `aot` property in your build configuration specified in `angular.json`. By default, `aot` is set to `true` for new CLI applications. See the [CLI command reference](https://angular.dev/cli) and [Building and serving Angular apps](https://angular.dev/tools/cli/build) for more information. ## [How AOT works](https://angular.dev/#how-aot-works) The Angular AOT compiler extracts **metadata** to interpret the parts of the application that Angular is supposed to manage. You can specify the metadata explicitly in **decorators** such as `@Component()` and `@Input()`, or implicitly in the constructor declarations of the decorated classes. The metadata tells Angular how to construct instances of your application classes and interact with them at runtime. In the following example, the `@Component()` metadata object and the class constructor tell Angular how to create and display an instance of `TypicalComponent`. @Component({ selector: 'app-typical', template: '<div>A typical component for {{data.name}}</div>'})export class TypicalComponent { @Input() data: TypicalData; private someService = inject(SomeService);} The Angular compiler extracts the metadata _once_ and generates a _factory_ for `TypicalComponent`. When it needs to create a `TypicalComponent` instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency. ### [Compilation phases](https://angular.dev/#compilation-phases) There are three phases of AOT compilation. | | Phase | Details | | --- | --- | --- | | 1 | code analysis | In this phase, the TypeScript compiler and _AOT collector_ create a representation of the source. The collector does not attempt to interpret the metadata it collects. It represents the metadata as best it can and records errors when it detects a metadata syntax violation. | | 2 | code generation | In this phase, the compiler's `StaticReflector` interprets the metadata collected in phase 1, performs additional validation of the metadata, and throws an error if it detects a metadata restriction violation. | | 3 | template type checking | In this optional phase, the Angular _template compiler_ uses the TypeScript compiler to validate the binding expressions in templates. You can enable this phase explicitly by setting the `strictTemplates` configuration option; see [Angular compiler options](https://angular.dev/reference/configs/angular-compiler-options). | ### [Metadata restrictions](https://angular.dev/#metadata-restrictions) You write metadata in a _subset_ of TypeScript that must conform to the following general constraints: * Limit [expression syntax](https://angular.dev/#expression-syntax-limitations) to the supported subset of JavaScript * Only reference exported symbols after [code folding](https://angular.dev/#code-folding) * Only call [functions supported](https://angular.dev/#supported-classes-and-functions) by the compiler * Input/Outputs and data-bound class members must be public or protected.For additional guidelines and instructions on preparing an application for AOT compilation, see [Angular: Writing AOT-friendly applications](https://medium.com/sparkles-blog/angular-writing-aot-friendly-applications-7b64c8afbe3f). **HELPFUL:** Errors in AOT compilation commonly occur because of metadata that does not conform to the compiler's requirements (as described more fully below). For help in understanding and resolving these problems, see [AOT Metadata Errors](https://angular.dev/tools/cli/aot-metadata-errors). ### [Configuring AOT compilation](https://angular.dev/#configuring-aot-compilation) You can provide options in the [TypeScript configuration file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) that controls the compilation process. See [Angular compiler options](https://angular.dev/reference/configs/angular-compiler-options) for a complete list of available options. ## [Phase 1: Code analysis](https://angular.dev/#phase-1-code-analysis) The TypeScript compiler does some of the analytic work of the first phase. It emits the `.d.ts` _type definition files_ with type information that the AOT compiler needs to generate application code. At the same time, the AOT **collector** analyzes the metadata recorded in the Angular decorators and outputs metadata information in **`.metadata.json`** files, one per `.d.ts` file. You can think of `.metadata.json` as a diagram of the overall structure of a decorator's metadata, represented as an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree). **HELPFUL:** Angular's [schema.ts](https://github.com/angular/angular/blob/main/packages/compiler-cli/src/metadata/schema.ts) describes the JSON format as a collection of TypeScript interfaces. ### [Expression syntax limitations](https://angular.dev/#expression-syntax-limitations) The AOT collector only understands a subset of JavaScript. Define metadata objects with the following limited syntax: | Syntax | Example | | --- | --- | | Literal object | `{cherry: true, apple: true, mincemeat: false}` | | Literal array | `['cherries', 'flour', 'sugar']` | | Spread in literal array | `['apples', 'flour', …]` | | Calls | `bake(ingredients)` | | New | `new Oven()` | | Property access | `pie.slice` | | Array index | `ingredients[0]` | | Identity reference | `Component` | | A template string | `` `pie is ${multiplier} times better than cake` `` | | Literal string | `'pi'` | | Literal number | `3.14153265` | | Literal boolean | `true` | | Literal null | `null` | | Supported prefix operator | `!cake` | | Supported binary operator | `a+b` | | Conditional operator | `a ? b : c` | | Parentheses | `(a+b)` | If an expression uses unsupported syntax, the collector writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that piece of metadata to generate the application code. **HELPFUL:** If you want `ngc` to report syntax errors immediately rather than produce a `.metadata.json` file with errors, set the `strictMetadataEmit` option in the TypeScript configuration file. "angularCompilerOptions": { … "strictMetadataEmit" : true} Angular libraries have this option to ensure that all Angular `.metadata.json` files are clean and it is a best practice to do the same when building your own libraries. ### [No arrow functions](https://angular.dev/#no-arrow-functions) The AOT compiler does not support [function expressions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/function) and [arrow functions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions), also called _lambda_ functions. Consider the following component decorator: @Component({ … providers: [{provide: server, useFactory: () => new Server()}]}) The AOT collector does not support the arrow function, `() => new Server()`, in a metadata expression. It generates an error node in place of the function. When the compiler later interprets this node, it reports an error that invites you to turn the arrow function into an _exported function_. You can fix the error by converting to this: export function serverFactory() { return new Server();}@Component({ … providers: [{provide: server, useFactory: serverFactory}]}) In version 5 and later, the compiler automatically performs this rewriting while emitting the `.js` file. ### [Code folding](https://angular.dev/#code-folding) The compiler can only resolve references to _**exported**_ symbols. The collector, however, can evaluate an expression during collection and record the result in the `.metadata.json`, rather than the original expression. This allows you to make limited use of non-exported symbols within expressions. For example, the collector can evaluate the expression `1 + 2 + 3 + 4` and replace it with the result, `10`. This process is called _folding_. An expression that can be reduced in this manner is _foldable_. The collector can evaluate references to module-local `const` declarations and initialized `var` and `let` declarations, effectively removing them from the `.metadata.json` file. Consider the following component definition: const template = '<div>{{hero.name}}</div>';@Component({ selector: 'app-hero', template: template})export class HeroComponent { @Input() hero: Hero;} The compiler could not refer to the `template` constant because it isn't exported. The collector, however, can fold the `template` constant into the metadata definition by in-lining its contents. The effect is the same as if you had written: @Component({ selector: 'app-hero', template: '<div>{{hero.name}}</div>'})export class HeroComponent { @Input() hero: Hero;} There is no longer a reference to `template` and, therefore, nothing to trouble the compiler when it later interprets the _collector's_ output in `.metadata.json`. You can take this example a step further by including the `template` constant in another expression: const template = '<div>{{hero.name}}</div>';@Component({ selector: 'app-hero', template: template + '<div>{{hero.title}}</div>'})export class HeroComponent { @Input() hero: Hero;} The collector reduces this expression to its equivalent _folded_ string: '<div>{{hero.name}}</div><div>{{hero.title}}</div>' #### [Foldable syntax](https://angular.dev/#foldable-syntax) The following table describes which expressions the collector can and cannot fold: | Syntax | Foldable | | --- | --- | | Literal object | yes | | Literal array | yes | | Spread in literal array | no | | Calls | no | | New | no | | Property access | yes, if target is foldable | | Array index | yes, if target and index are foldable | | Identity reference | yes, if it is a reference to a local | | A template with no substitutions | yes | | A template with substitutions | yes, if the substitutions are foldable | | Literal string | yes | | Literal number | yes | | Literal boolean | yes | | Literal null | yes | | Supported prefix operator | yes, if operand is foldable | | Supported binary operator | yes, if both left and right are foldable | | Conditional operator | yes, if condition is foldable | | Parentheses | yes, if the expression is foldable | If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract*syntax*tree) for the compiler to resolve. ## [Phase 2: code generation](https://angular.dev/#phase-2-code-generation) The collector makes no attempt to understand the metadata that it collects and outputs to `.metadata.json`. It represents the metadata as best it can and records errors when it detects a metadata syntax violation. It's the compiler's job to interpret the `.metadata.json` in the code generation phase. The compiler understands all syntax forms that the collector supports, but it may reject _syntactically_ correct metadata if the _semantics_ violate compiler rules. ### [Public or protected symbols](https://angular.dev/#public-or-protected-symbols) The compiler can only reference _exported symbols_. * Decorated component class members must be public or protected. You cannot make an `@Input()` property private. * Data bound properties must also be public or protected ### [Supported classes and functions](https://angular.dev/#supported-classes-and-functions) The collector can represent a function call or object creation with `new` as long as the syntax is valid. The compiler, however, can later refuse to generate a call to a _particular_ function or creation of a _particular_ object. The compiler can only create instances of certain classes, supports only core decorators, and only supports calls to macros (functions or static methods) that return expressions. | Compiler action | Details | | --- | --- | | New instances | The compiler only allows metadata that create instances of the class `InjectionToken` from `@angular/core`. | | Supported decorators | The compiler only supports metadata for the [Angular decorators in the `@angular/core` module](https://angular.dev/api/core#decorators). | | Function calls | Factory functions must be exported, named functions. The AOT compiler does not support lambda expressions ("arrow functions") for factory functions. | ### [Functions and static method calls](https://angular.dev/#functions-and-static-method-calls) The collector accepts any function or static method that contains a single `return` statement. The compiler, however, only supports macros in the form of functions or static methods that return an _expression_. For example, consider the following function: export function wrapInArray<T>(value: T): T[] { return [value];} You can call the `wrapInArray` in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset. You might use `wrapInArray()` like this: @NgModule({ declarations: wrapInArray(TypicalComponent)})export class TypicalModule {} The compiler treats this usage as if you had written: @NgModule({ declarations: [TypicalComponent]})export class TypicalModule {} The Angular [`RouterModule`](https://angular.dev/api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes. Review the [source code](https://github.com/angular/angular/blob/main/packages/router/src/router_module.ts#L139 "RouterModule.forRoot") for these methods to see how macros can simplify configuration of complex [NgModules](https://angular.dev/guide/ngmodules). ### [Metadata rewriting](https://angular.dev/#metadata-rewriting) The compiler treats object literals containing the fields `useClass`, `useValue`, `useFactory`, and `data` specially, converting the expression initializing one of these fields into an exported variable that replaces the expression. This process of rewriting these expressions removes all the restrictions on what can be in them because the compiler doesn't need to know the expression's value — it just needs to be able to generate a reference to the value. You might write something like: class TypicalServer {}@NgModule({ providers: [{provide: SERVER, useFactory: () => TypicalServer}]})export class TypicalModule {} Without rewriting, this would be invalid because lambdas are not supported and `TypicalServer` is not exported. To allow this, the compiler automatically rewrites this to something like: class TypicalServer {}export const θ0 = () => new TypicalServer();@NgModule({ providers: [{provide: SERVER, useFactory: θ0}]})export class TypicalModule {} This allows the compiler to generate a reference to `θ0` in the factory without having to know what the value of `θ0` contains. The compiler does the rewriting during the emit of the `.js` file. It does not, however, rewrite the `.d.ts` file, so TypeScript doesn't recognize it as being an export. And it does not interfere with the ES module's exported API. ## [Phase 3: Template type checking](https://angular.dev/#phase-3-template-type-checking) One of the Angular compiler's most helpful features is the ability to type-check expressions within templates, and catch any errors before they cause crashes at runtime. In the template type-checking phase, the Angular template compiler uses the TypeScript compiler to validate the binding expressions in templates. Enable this phase explicitly by adding the compiler option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's TypeScript configuration file (see [Angular Compiler Options](https://angular.dev/reference/configs/angular-compiler-options)). Template validation produces error messages when a type error is detected in a template binding expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts` file. For example, consider the following component: @Component({ selector: 'my-component', template: '{{person.addresss.street}}'})class MyComponent { person?: Person;} This produces the following error: my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'? The file name reported in the error message, `my.component.ts.MyComponent.html`, is a synthetic file generated by the template compiler that holds contents of the `MyComponent` class template. The compiler never writes this file to disk. The line and column numbers are relative to the template string in the `@Component` annotation of the class, `MyComponent` in this case. If a component uses `templateUrl` instead of `template`, the errors are reported in the HTML file referenced by the `templateUrl` instead of a synthetic file. The error location is the beginning of the text node that contains the interpolation expression with the error. If the error is in an attribute binding such as `[value]="person.address.street"`, the error location is the location of the attribute that contains the error. The validation uses the TypeScript type checker and the options supplied to the TypeScript compiler to control how detailed the type validation is. For example, if the `strictTypeChecks` is specified, the error my.component.ts.MyComponent.html(1,1): : Object is possibly 'undefined' is reported as well as the above error message. ### [Type narrowing](https://angular.dev/#type-narrowing) The expression used in an `ngIf` directive is used to narrow type unions in the Angular template compiler, the same way the `if` expression does in TypeScript. For example, to avoid `Object is possibly 'undefined'` error in the template above, modify it to only emit the interpolation if the value of `person` is initialized as shown below: @Component({ selector: 'my-component', template: '<span *ngIf="person"> {{person.address.street}} </span>'})class MyComponent { person?: Person;} Using `*ngIf` allows the TypeScript compiler to infer that the `person` used in the binding expression will never be `undefined`. For more information about input type narrowing, see [Improving template type checking for custom directives](https://angular.dev/guide/directives/structural-directives#directive-type-checks). ### [Non-null type assertion operator](https://angular.dev/#non-null-type-assertion-operator) Use the non-null type assertion operator to suppress the `Object is possibly 'undefined'` error when it is inconvenient to use `*ngIf` or when some constraint in the component ensures that the expression is always non-null when the binding expression is interpolated. In the following example, the `person` and `address` properties are always set together, implying that `address` is always non-null if `person` is non-null. There is no convenient way to describe this constraint to TypeScript and the template compiler, but the error is suppressed in the example by using `address!.street`. @Component({ selector: 'my-component', template: '<span *ngIf="person"> {{person.name}} lives on {{address!.street}} </span>'})class MyComponent { person?: Person; address?: Address; setData(person: Person, address: Address) { this.person = person; this.address = address; }} The non-null assertion operator should be used sparingly as refactoring of the component might break this constraint. In this example it is recommended to include the checking of `address` in the `*ngIf` as shown below: @Component({ selector: 'my-component', template: '<span *ngIf="person && address"> {{person.name}} lives on {{address.street}} </span>'})class MyComponent { person?: Person; address?: Address; setData(person: Person, address: Address) { this.person = person; this.address = address; }} --- ## Page: https://angular.dev/tools/cli/aot-metadata-errors The following are metadata errors you may encounter, with explanations and suggested corrections. ## [Expression form not supported](https://angular.dev/#expression-form-not-supported) **HELPFUL:** The compiler encountered an expression it didn't understand while evaluating Angular metadata. Language features outside of the compiler's [restricted expression syntax](https://angular.dev/tools/cli/aot-compiler#expression-syntax) can produce this error, as seen in the following example: // ERRORexport class Fooish { … }…const prop = typeof Fooish; // typeof is not valid in metadata … // bracket notation is not valid in metadata { provide: 'token', useValue: { [prop]: 'value' } }; … You can use `typeof` and bracket notation in normal application code. You just can't use those features within expressions that define Angular metadata. Avoid this error by sticking to the compiler's [restricted expression syntax](https://angular.dev/tools/cli/aot-compiler#expression-syntax) when writing Angular metadata and be wary of new or unusual TypeScript features. ## [Reference to a local (non-exported) symbol](https://angular.dev/#reference-to-a-local-non-exported-symbol) **HELPFUL:** Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol. The compiler encountered a reference to a locally defined symbol that either wasn't exported or wasn't initialized. Here's a `provider` example of the problem. // ERRORlet foo: number; // neither exported nor initialized@Component({ selector: 'my-component', template: … , providers: [ { provide: Foo, useValue: foo } ]})export class MyComponent {} The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. _That_ factory module can't reach back to _this_ source module to access the local (non-exported) `foo` variable. You could fix the problem by initializing `foo`. let foo = 42; // initialized The compiler will [fold](https://angular.dev/tools/cli/aot-compiler#code-folding) the expression into the provider as if you had written this. providers: [ { provide: Foo, useValue: 42 }] Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value. // CORRECTEDexport let foo: number; // exported@Component({ selector: 'my-component', template: … , providers: [ { provide: Foo, useValue: foo } ]})export class MyComponent {} Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate _references_ to the exported variables in these expressions. It doesn't need the _values_ of those variables. Adding `export` doesn't work when the compiler needs the _actual value_ in order to generate code. For example, it doesn't work for the `template` property. // ERRORexport let someTemplate: string; // exported but not initialized@Component({ selector: 'my-component', template: someTemplate})export class MyComponent {} The compiler needs the value of the `template` property _right now_ to generate the component factory. The variable reference alone is insufficient. Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](https://angular.dev/#only-initialized-variables)". ## [Only initialized variables and constants](https://angular.dev/#only-initialized-variables-and-constants) **HELPFUL:** _Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler._ The compiler found a reference to an exported variable or static field that wasn't initialized. It needs the value of that variable to generate code. The following example tries to set the component's `template` property to the value of the exported `someTemplate` variable which is declared but _unassigned_. // ERRORexport let someTemplate: string;@Component({ selector: 'my-component', template: someTemplate})export class MyComponent {} You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there. // ERROR - not initialized there eitherimport { someTemplate } from './config';@Component({ selector: 'my-component', template: someTemplate})export class MyComponent {} The compiler cannot wait until runtime to get the template information. It must statically derive the value of the `someTemplate` variable from the source code so that it can generate the component factory, which includes instructions for building the element based on the template. To correct this error, provide the initial value of the variable in an initializer clause _on the same line_. // CORRECTEDexport let someTemplate = '<h1>Greetings from Angular</h1>';@Component({ selector: 'my-component', template: someTemplate})export class MyComponent {} ## [Reference to a non-exported class](https://angular.dev/#reference-to-a-non-exported-class) **HELPFUL:** _Reference to a non-exported class `<class name>`._ _Consider exporting the class._ Metadata referenced a class that wasn't exported. For example, you may have defined a class and used it as an injection token in a providers array but neglected to export that class. // ERRORabstract class MyStrategy { } … providers: [ { provide: MyStrategy, useValue: … } ] … Angular generates a class factory in a separate module and that factory [can only access exported classes](https://angular.dev/tools/cli/aot-compiler#exported-symbols). To correct this error, export the referenced class. // CORRECTEDexport abstract class MyStrategy { } … providers: [ { provide: MyStrategy, useValue: … } ] … ## [Reference to a non-exported function](https://angular.dev/#reference-to-a-non-exported-function) **HELPFUL:** _Metadata referenced a function that wasn't exported._ For example, you may have set a providers `useFactory` property to a locally defined function that you neglected to export. // ERRORfunction myStrategy() { … } … providers: [ { provide: MyStrategy, useFactory: myStrategy } ] … Angular generates a class factory in a separate module and that factory [can only access exported functions](https://angular.dev/tools/cli/aot-compiler#exported-symbols). To correct this error, export the function. // CORRECTEDexport function myStrategy() { … } … providers: [ { provide: MyStrategy, useFactory: myStrategy } ] … ## [Function calls are not supported](https://angular.dev/#function-calls-are-not-supported) **HELPFUL:** _Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function._ The compiler does not currently support [function expressions or lambda functions](https://angular.dev/tools/cli/aot-compiler#function-expression). For example, you cannot set a provider's `useFactory` to an anonymous function or arrow function like this. // ERROR … providers: [ { provide: MyStrategy, useFactory: function() { … } }, { provide: OtherStrategy, useFactory: () => { … } } ] … You also get this error if you call a function or method in a provider's `useValue`. // ERRORimport { calculateValue } from './utilities'; … providers: [ { provide: SomeValue, useValue: calculateValue() } ] … To correct this error, export a function from the module and refer to the function in a `useFactory` provider instead. // CORRECTEDimport { calculateValue } from './utilities';export function myStrategy() { … }export function otherStrategy() { … }export function someValueFactory() { return calculateValue();} … providers: [ { provide: MyStrategy, useFactory: myStrategy }, { provide: OtherStrategy, useFactory: otherStrategy }, { provide: SomeValue, useFactory: someValueFactory } ] … ## [Destructured variable or constant not supported](https://angular.dev/#destructured-variable-or-constant-not-supported) **HELPFUL:** _Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring._ The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring). For example, you cannot write something like this: // ERRORimport { configuration } from './configuration';// destructured assignment to foo and barconst {foo, bar} = configuration; … providers: [ {provide: Foo, useValue: foo}, {provide: Bar, useValue: bar}, ] … To correct this error, refer to non-destructured values. // CORRECTEDimport { configuration } from './configuration'; … providers: [ {provide: Foo, useValue: configuration.foo}, {provide: Bar, useValue: configuration.bar}, ] … ## [Could not resolve type](https://angular.dev/#could-not-resolve-type) **HELPFUL:** _The compiler encountered a type and can't determine which module exports that type._ This can happen if you refer to an ambient type. For example, the `Window` type is an ambient type declared in the global `.d.ts` file. You'll get an error if you reference it in the component constructor, which the compiler must statically analyze. // ERROR@Component({ })export class MyComponent { constructor (private win: Window) { … }} TypeScript understands ambient types so you don't import them. The Angular compiler does not understand a type that you neglect to export or import. In this case, the compiler doesn't understand how to inject something with the `Window` token. Do not refer to ambient types in metadata expressions. If you must inject an instance of an ambient type, you can finesse the problem in four steps: 1. Create an injection token for an instance of the ambient type. 2. Create a factory function that returns that instance. 3. Add a `useFactory` provider with that factory function. 4. Use `@Inject` to inject the instance. Here's an illustrative example. // CORRECTEDimport { Inject } from '@angular/core';export const WINDOW = new InjectionToken('Window');export function _window() { return window; }@Component({ … providers: [ { provide: WINDOW, useFactory: _window } ]})export class MyComponent { constructor (@Inject(WINDOW) private win: Window) { … }} The `Window` type in the constructor is no longer a problem for the compiler because it uses the `@Inject(WINDOW)` to generate the injection code. Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs). import { Inject } from '@angular/core';import { DOCUMENT } from '@angular/common';@Component({ … })export class MyComponent { constructor (@Inject(DOCUMENT) private doc: Document) { … }} ## [Name expected](https://angular.dev/#name-expected) **HELPFUL:** _The compiler expected a name in an expression it was evaluating._ This can happen if you use a number as a property name as in the following example. // ERRORprovider: [{ provide: Foo, useValue: { 0: 'test' } }] Change the name of the property to something non-numeric. // CORRECTEDprovider: [{ provide: Foo, useValue: { '0': 'test' } }] ## [Unsupported enum member name](https://angular.dev/#unsupported-enum-member-name) **HELPFUL:** _Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html) that you referenced in metadata._ The compiler can understand simple enum values but not complex values such as those derived from computed properties. // ERRORenum Colors { Red = 1, White, Blue = "Blue".length // computed} … providers: [ { provide: BaseColor, useValue: Colors.White } // ok { provide: DangerColor, useValue: Colors.Red } // ok { provide: StrongColor, useValue: Colors.Blue } // bad ] … Avoid referring to enums with complicated initializers or computed properties. ## [Tagged template expressions are not supported](https://angular.dev/#tagged-template-expressions-are-not-supported) **HELPFUL:** _Tagged template expressions are not supported in metadata._ The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as the following. // ERRORconst expression = 'funky';const raw = String.raw`A tagged template ${expression} string`; … template: '<div>' + raw + '</div>' … [`String.raw()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/raw) is a _tag function_ native to JavaScript ES2015. The AOT compiler does not support tagged template expressions; avoid them in metadata expressions. ## [Symbol reference expected](https://angular.dev/#symbol-reference-expected) **HELPFUL:** _The compiler expected a reference to a symbol at the location specified in the error message._ This error can occur if you use an expression in the `extends` clause of a class. --- ## Page: https://angular.dev/tools/cli/cli ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tutorials/learn-angular ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/reference/versions ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/new ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/reference/configs/workspace-config ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/generate ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/add ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/reference/configs/file-structure ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/test ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/deploy ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/e2e ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/build-system-migration#manual-migration-to-the-compatibility-builder ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/libraries/angular-package-format ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/environments ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/generate/config ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/cli-builder ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/deployment#production-optimizations ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/guide/routing/common-router-tasks#displaying-a-404-page ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/aot-compiler ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/build ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/errors/NG0100 ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/api#core-global ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/devtools ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/guide/ssr ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/generate/environments ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/schematics-authoring ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/run ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/libraries/creating-libraries ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/schematics-for-libraries ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/reference/configs/angular-compiler-options ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/guide/directives/structural-directives#directive-type-checks ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/cli/serve ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/build ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/tools/cli/aot-metadata-errors ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/api/core#decorators ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/api/router/RouterModule ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/cli/guide/ngmodules ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries Many applications need to solve the same general problems, such as presenting a unified user interface, presenting data, and allowing data entry. Developers can create general solutions for particular domains that can be adapted for re-use in different applications. Such a solution can be built as Angular _libraries_ and these libraries can be published and shared as _npm packages_. An Angular library is an Angular project that differs from an application in that it cannot run on its own. A library must be imported and used in an application. Libraries extend Angular's base features. For example, to add [reactive forms](https://angular.dev/guide/forms/reactive-forms) to an application, add the library package using `ng add @angular/forms`, then import the `ReactiveFormsModule` from the `@angular/forms` library in your application code. Similarly, adding the [service worker](https://angular.dev/ecosystem/service-workers) library to an Angular application is one of the steps for turning an application into a [Progressive Web App](https://developers.google.com/web/progressive-web-apps) (PWA). [Angular Material](https://material.angular.io/) is an example of a large, general-purpose library that provides sophisticated, reusable, and adaptable UI components. Any application developer can use these and other libraries that have been published as npm packages by the Angular team or by third parties. See [Using Published Libraries](https://angular.dev/tools/libraries/using-libraries). **HELPFUL:** Libraries are intended to be used by Angular applications. To add Angular features to non-Angular web applications, use [Angular custom elements](https://angular.dev/guide/elements). ## [Creating libraries](https://angular.dev/#creating-libraries) If you have developed features that are suitable for reuse, you can create your own libraries. These libraries can be used locally in your workspace, or you can publish them as [npm packages](https://angular.dev/reference/configs/npm-packages) to share with other projects or other Angular developers. These packages can be published to the npm registry, a private npm Enterprise registry, or a private package management system that supports npm packages. See [Creating Libraries](https://angular.dev/tools/libraries/creating-libraries). Deciding to package features as a library is an architectural decision. It is comparable to deciding whether a feature is a component or a service, or deciding on the scope of a component. Packaging features as a library forces the artifacts in the library to be decoupled from the application's business logic. This can help to avoid various bad practices or architecture mistakes that can make it difficult to decouple and reuse code in the future. Putting code into a separate library is more complex than simply putting everything in one application. It requires more of an investment in time and thought for managing, maintaining, and updating the library. This complexity can pay off when the library is being used in multiple applications. --- ## Page: https://angular.dev/tools/libraries/creating-libraries This page provides a conceptual overview of how to create and publish new libraries to extend Angular functionality. If you find that you need to solve the same problem in more than one application (or want to share your solution with other developers), you have a candidate for a library. A simple example might be a button that sends users to your company website, that would be included in all applications that your company builds. ## [Getting started](https://angular.dev/#getting-started) Use the Angular CLI to generate a new library skeleton in a new workspace with the following commands. ng new my-workspace --no-create-applicationcd my-workspaceng generate library my-lib ### Naming your library You should be very careful when choosing the name of your library if you want to publish it later in a public package registry such as npm. See [Publishing your library](https://angular.dev/tools/libraries/creating-libraries#publishing-your-library). Avoid using a name that is prefixed with `ng-`, such as `ng-library`. The `ng-` prefix is a reserved keyword used from the Angular framework and its libraries. The `ngx-` prefix is preferred as a convention used to denote that the library is suitable for use with Angular. It is also an excellent indication to consumers of the registry to differentiate between libraries of different JavaScript frameworks. The `ng generate` command creates the `projects/my-lib` folder in your workspace, which contains a component. Use the monorepo model to use the same workspace for multiple projects. See [Setting up for a multi-project workspace](https://angular.dev/reference/configs/file-structure#multiple-projects). When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type `library`. "projects": { … "my-lib": { "root": "projects/my-lib", "sourceRoot": "projects/my-lib/src", "projectType": "library", "prefix": "lib", "architect": { "build": { "builder": "@angular-devkit/build-angular:ng-packagr", … Build, test, and lint the project with CLI commands: ng build my-lib --configuration developmentng test my-libng lint my-lib Notice that the configured builder for the project is different from the default builder for application projects. This builder, among other things, ensures that the library is always built with the [AOT compiler](https://angular.dev/tools/cli/aot-compiler). To make library code reusable you must define a public API for it. This "user layer" defines what is available to consumers of your library. A user of your library should be able to access public functionality (such as service providers and general utility functions) through a single import path. The public API for your library is maintained in the `public-api.ts` file in your library folder. Anything exported from this file is made public when your library is imported into an application. Your library should supply documentation (typically a README file) for installation and maintenance. ## [Refactoring parts of an application into a library](https://angular.dev/#refactoring-parts-of-an-application-into-a-library) To make your solution reusable, you need to adjust it so that it does not depend on application-specific code. Here are some things to consider in migrating application functionality to a library. * Declarations such as components and pipes should be designed as stateless, meaning they don't rely on or alter external variables. If you do rely on state, you need to evaluate every case and decide whether it is application state or state that the library would manage. * Any observables that the components subscribe to internally should be cleaned up and disposed of during the lifecycle of those components * Components should expose their interactions through inputs for providing context, and outputs for communicating events to other components * Check all internal dependencies. * For custom classes or interfaces used in components or service, check whether they depend on additional classes or interfaces that also need to be migrated * Similarly, if your library code depends on a service, that service needs to be migrated * If your library code or its templates depend on other libraries (such as Angular Material, for instance), you must configure your library with those dependencies * Consider how you provide services to client applications. * Services should declare their own providers, rather than declaring providers in the NgModule or a component. Declaring a provider makes that service _tree-shakable_. This practice lets the compiler leave the service out of the bundle if it never gets injected into the application that imports the library. For more about this, see [Tree-shakable providers](https://angular.dev/guide/di/lightweight-injection-tokens). * If you register global service providers expose a `provideXYZ()` provider function. * If your library provides optional services that might not be used by all client applications, support proper tree-shaking for that case by using the [lightweight token design pattern](https://angular.dev/guide/di/lightweight-injection-tokens) ## [Integrating with the CLI using code-generation schematics](https://angular.dev/#integrating-with-the-cli-using-code-generation-schematics) A library typically includes _reusable code_ that defines components, services, and other Angular artifacts (pipes, directives) that you import into a project. A library is packaged into an npm package for publishing and sharing. This package can also include schematics that provide instructions for generating or transforming code directly in your project, in the same way that the CLI creates a generic new component with `ng generate component`. A schematic that is packaged with a library can, for example, provide the Angular CLI with the information it needs to generate a component that configures and uses a particular feature, or set of features, defined in that library. One example of this is [Angular Material's navigation schematic](https://material.angular.io/guide/schematics#navigation-schematic) which configures the CDK's [BreakpointObserver](https://material.angular.io/cdk/layout/overview#breakpointobserver) and uses it with Material's [MatSideNav](https://material.angular.io/components/sidenav/overview) and [MatToolbar](https://material.angular.io/components/toolbar/overview) components. Create and include the following kinds of schematics: * Include an installation schematic so that `ng add` can add your library to a project * Include generation schematics in your library so that `ng generate` can scaffold your defined artifacts (components, services, tests) in a project * Include an update schematic so that `ng update` can update your library's dependencies and provide migrations for breaking changes in new releases What you include in your library depends on your task. For example, you could define a schematic to create a dropdown that is pre-populated with canned data to show how to add it to an application. If you want a dropdown that would contain different passed-in values each time, your library could define a schematic to create it with a given configuration. Developers could then use `ng generate` to configure an instance for their own application. Suppose you want to read a configuration file and then generate a form based on that configuration. If that form needs additional customization by the developer who is using your library, it might work best as a schematic. However, if the form will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form. In general, the more complex the customization, the more useful the schematic approach. For more information, see [Schematics Overview](https://angular.dev/tools/cli/schematics) and [Schematics for Libraries](https://angular.dev/tools/cli/schematics-for-libraries). ## [Publishing your library](https://angular.dev/#publishing-your-library) Use the Angular CLI and the npm package manager to build and publish your library as an npm package. Angular CLI uses a tool called [ng-packagr](https://github.com/ng-packagr/ng-packagr/blob/master/README.md) to create packages from your compiled code that can be published to npm. See [Building libraries with Ivy](https://angular.dev/tools/libraries/creating-libraries#publishing-libraries) for information on the distribution formats supported by `ng-packagr` and guidance on how to choose the right format for your library. You should always build libraries for distribution using the `production` configuration. This ensures that generated output uses the appropriate optimizations and the correct package format for npm. ng build my-libcd dist/my-libnpm publish ## [Managing assets in a library](https://angular.dev/#managing-assets-in-a-library) In your Angular library, the distributable can include additional assets like theming files, Sass mixins, or documentation (like a changelog). For more information [copy assets into your library as part of the build](https://github.com/ng-packagr/ng-packagr/blob/master/docs/copy-assets.md) and [embed assets in component styles](https://github.com/ng-packagr/ng-packagr/blob/master/docs/embed-assets-css.md). **IMPORTANT:** When including additional assets like Sass mixins or pre-compiled CSS. You need to add these manually to the conditional ["exports"](https://angular.dev/tools/libraries/angular-package-format#quotexportsquot) in the `package.json` of the primary entrypoint. `ng-packagr` will merge handwritten `"exports"` with the auto-generated ones, allowing for library authors to configure additional export subpaths, or custom conditions. "exports": { ".": { "sass": "./_index.scss", }, "./theming": { "sass": "./_theming.scss" }, "./prebuilt-themes/indigo-pink.css": { "style": "./prebuilt-themes/indigo-pink.css" }} The above is an extract from the [@angular/material](https://unpkg.com/browse/@angular/material/package.json) distributable. ## [Peer dependencies](https://angular.dev/#peer-dependencies) Angular libraries should list any `@angular/*` dependencies the library depends on as peer dependencies. This ensures that when modules ask for Angular, they all get the exact same module. If a library lists `@angular/core` in `dependencies` instead of `peerDependencies`, it might get a different Angular module instead, which would cause your application to break. ## [Using your own library in applications](https://angular.dev/#using-your-own-library-in-applications) You don't have to publish your library to the npm package manager to use it in the same workspace, but you do have to build it first. To use your own library in an application: * Build the library. You cannot use a library before it is built. ng build my-lib * In your applications, import from the library by name: import { myExport } from 'my-lib'; ### [Building and rebuilding your library](https://angular.dev/#building-and-rebuilding-your-library) The build step is important if you haven't published your library as an npm package and then installed the package back into your application from npm. For instance, if you clone your git repository and run `npm install`, your editor shows the `my-lib` imports as missing if you haven't yet built your library. **HELPFUL:** When you import something from a library in an Angular application, Angular looks for a mapping between the library name and a location on disk. When you install a library package, the mapping is in the `node_modules` folder. When you build your own library, it has to find the mapping in your `tsconfig` paths. Generating a library with the Angular CLI automatically adds its path to the `tsconfig` file. The Angular CLI uses the `tsconfig` paths to tell the build system where to find the library. For more information, see [Path mapping overview](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping). If you find that changes to your library are not reflected in your application, your application is probably using an old build of the library. You can rebuild your library whenever you make changes to it, but this extra step takes time. _Incremental builds_ functionality improves the library-development experience. Every time a file is changed a partial build is performed that emits the amended files. Incremental builds can be run as a background process in your development environment. To take advantage of this feature add the `--watch` flag to the build command: ng build my-lib --watch **IMPORTANT:** The CLI `build` command uses a different builder and invokes a different build tool for libraries than it does for applications. * The build system for applications, `@angular-devkit/build-angular`, is based on `webpack`, and is included in all new Angular CLI projects * The build system for libraries is based on `ng-packagr`. It is only added to your dependencies when you add a library using `ng generate library my-lib`. The two build systems support different things, and even where they support the same things, they do those things differently. This means that the TypeScript source can result in different JavaScript code in a built library than it would in a built application. For this reason, an application that depends on a library should only use TypeScript path mappings that point to the _built library_. TypeScript path mappings should _not_ point to the library source `.ts` files. ## [Publishing libraries](https://angular.dev/#publishing-libraries) There are two distribution formats to use when publishing a library: | Distribution formats | Details | | --- | --- | | Partial-Ivy (recommended) | Contains portable code that can be consumed by Ivy applications built with any version of Angular from v12 onwards. | | Full-Ivy | Contains private Angular Ivy instructions, which are not guaranteed to work across different versions of Angular. This format requires that the library and application are built with the _exact_ same version of Angular. This format is useful for environments where all library and application code is built directly from source. | For publishing to npm use the partial-Ivy format as it is stable between patch versions of Angular. Avoid compiling libraries with full-Ivy code if you are publishing to npm because the generated Ivy instructions are not part of Angular's public API, and so might change between patch versions. ## [Ensuring library version compatibility](https://angular.dev/#ensuring-library-version-compatibility) The Angular version used to build an application should always be the same or greater than the Angular versions used to build any of its dependent libraries. For example, if you had a library using Angular version 13, the application that depends on that library should use Angular version 13 or later. Angular does not support using an earlier version for the application. If you intend to publish your library to npm, compile with partial-Ivy code by setting `"compilationMode": "partial"` in `tsconfig.prod.json`. This partial format is stable between different versions of Angular, so is safe to publish to npm. Code with this format is processed during the application build using the same version of the Angular compiler, ensuring that the application and all of its libraries use a single version of Angular. Avoid compiling libraries with full-Ivy code if you are publishing to npm because the generated Ivy instructions are not part of Angular's public API, and so might change between patch versions. If you've never published a package in npm before, you must create a user account. Read more in [Publishing npm Packages](https://docs.npmjs.com/getting-started/publishing-npm-packages). ## [Consuming partial-Ivy code outside the Angular CLI](https://angular.dev/#consuming-partial-ivy-code-outside-the-angular-cli) An application installs many Angular libraries from npm into its `node_modules` directory. However, the code in these libraries cannot be bundled directly along with the built application as it is not fully compiled. To finish compilation, use the Angular linker. For applications that don't use the Angular CLI, the linker is available as a [Babel](https://babeljs.io/) plugin. The plugin is to be imported from `@angular/compiler-cli/linker/babel`. The Angular linker Babel plugin supports build caching, meaning that libraries only need to be processed by the linker a single time, regardless of other npm operations. Example of integrating the plugin into a custom [webpack](https://webpack.js.org/) build by registering the linker as a [Babel](https://babeljs.io/) plugin using [babel-loader](https://webpack.js.org/loaders/babel-loader/#options). import linkerPlugin from '@angular/compiler-cli/linker/babel';export default { module: { rules: [ { test: /\.m?js$/, use: { loader: 'babel-loader', options: { plugins: [linkerPlugin], compact: false, cacheDirectory: true, } } } ] }} **HELPFUL:** The Angular CLI integrates the linker plugin automatically, so if consumers of your library are using the CLI, they can install Ivy-native libraries from npm without any additional configuration. --- ## Page: https://angular.dev/tools/libraries/using-libraries When you build your Angular application, take advantage of sophisticated first-party libraries, as well as a rich ecosystem of third-party libraries. [Angular Material](https://material.angular.io/ "Angular") is an example of a sophisticated first-party library. ## [Install libraries](https://angular.dev/#install-libraries) Libraries are published as [npm packages](https://angular.dev/reference/configs/npm-packages "Workspace"), usually together with schematics that integrate them with the Angular CLI. To integrate reusable library code into an application, you need to install the package and import the provided functionality in the location you use it. For most published Angular libraries, use the `ng add <lib_name>` Angular CLI command. The `ng add` Angular CLI command uses a package manager to install the library package and invokes schematics that are included in the package to other scaffolding within the project code. Examples of package managers include [npm](https://www.npmjs.com/ "npm") or [yarn](https://yarnpkg.com/ "Yarn"). Additional scaffolding within the project code includes import statements, fonts, and themes. A published library typically provides a `README` file or other documentation on how to add that library to your application. For an example, see the [Angular Material](https://material.angular.io/ "Angular") documentation. ### [Library typings](https://angular.dev/#library-typings) Typically, library packages include typings in `.d.ts` files; see examples in `node_modules/@angular/material`. If the package of your library does not include typings and your IDE complains, you might need to install the `@types/<lib_name>` package with the library. For example, suppose you have a library named `d3`: npm install d3 --savenpm install @types/d3 --save-dev Types defined in a `@types/` package for a library installed into the workspace are automatically added to the TypeScript configuration for the project that uses that library. TypeScript looks for types in the `node_modules/@types` directory by default, so you do not have to add each type package individually. If a library does not have typings available at `@types/`, you may use it by manually adding typings for it. To do this: 1. Create a `typings.d.ts` file in your `src/` directory. This file is automatically included as global type definition. 2. Add the following code in `src/typings.d.ts`: declare module 'host' { export interface Host { protocol?: string; hostname?: string; pathname?: string; } export function parse(url: string, queryString?: string): Host; } 3. In the component or file that uses the library, add the following code: import * as host from 'host'; const parsedUrl = host.parse('https://angular.io'); console.log(parsedUrl.hostname); Define more typings as needed. ## [Updating libraries](https://angular.dev/#updating-libraries) A library is able to be updated by the publisher, and also has individual dependencies which need to be kept current. To check for updates to your installed libraries, use the [`ng update`](https://angular.dev/cli/update "ng") Angular CLI command. Use `ng update <lib_name>` Angular CLI command to update individual library versions. The Angular CLI checks the latest published release of the library, and if the latest version is newer than your installed version, downloads it and updates your `package.json` to match the latest version. When you update Angular to a new version, you need to make sure that any libraries you are using are current. If libraries have interdependencies, you might have to update them in a particular order. See the [Angular Update Guide](https://angular.dev/update-guide "Angular") for help. ## [Adding a library to the runtime global scope](https://angular.dev/#adding-a-library-to-the-runtime-global-scope) If a legacy JavaScript library is not imported into an application, you may add it to the runtime global scope and load it as if it was added in a script tag. Configure the Angular CLI to do this at build time using the `scripts` and `styles` options of the build target in the [`angular.json`](https://angular.dev/reference/configs/workspace-config "Angular") workspace build configuration file. For example, to use the [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction "Introduction") library 1. Install the library and the associated dependencies using the npm package manager: npm install jquery --save npm install popper.js --save npm install bootstrap --save 2. In the `angular.json` configuration file, add the associated script files to the `scripts` array: "scripts": [ "node_modules/jquery/dist/jquery.slim.js", "node_modules/popper.js/dist/umd/popper.js", "node_modules/bootstrap/dist/js/bootstrap.js" ], 3. Add the `bootstrap.css` CSS file to the `styles` array: "styles": [ "node_modules/bootstrap/dist/css/bootstrap.css", "src/styles.css" ], 4. Run or restart the `ng serve` Angular CLI command to see Bootstrap 4 work in your application. ### [Using runtime-global libraries inside your app](https://angular.dev/#using-runtime-global-libraries-inside-your-app) After you import a library using the "scripts" array, do **not** import it using an import statement in your TypeScript code. The following code snippet is an example import statement. import * as $ from 'jquery'; If you import it using import statements, you have two different copies of the library: one imported as a global library, and one imported as a module. This is especially bad for libraries with plugins, like JQuery, because each copy includes different plugins. Instead, run the `npm install @types/jquery` Angular CLI command to download typings for your library and then follow the library installation steps. This gives you access to the global variables exposed by that library. ### [Defining typings for runtime-global libraries](https://angular.dev/#defining-typings-for-runtime-global-libraries) If the global library you need to use does not have global typings, you can declare them manually as `any` in `src/typings.d.ts`. For example: declare var libraryName: any; Some scripts extend other libraries; for instance with JQuery plugins: $('.test').myPlugin(); In this case, the installed `@types/jquery` does not include `myPlugin`, so you need to add an interface in `src/typings.d.ts`. For example: interface JQuery { myPlugin(options?: any): any;} If you do not add the interface for the script-defined extension, your IDE shows an error: [TS][Error] Property 'myPlugin' does not exist on type 'JQuery' --- ## Page: https://angular.dev/tools/libraries/angular-package-format This document describes the Angular Package Format (APF). APF is an Angular specific specification for the structure and format of npm packages that is used by all first-party Angular packages (`@angular/core`, `@angular/material`, etc.) and most third-party Angular libraries. APF enables a package to work seamlessly under most common scenarios that use Angular. Packages that use APF are compatible with the tooling offered by the Angular team as well as wider JavaScript ecosystem. It is recommended that third-party library developers follow the same npm package format. **HELPFUL:** APF is versioned along with the rest of Angular, and every major version improves the package format. You can find the versions of the specification prior to v13 in this [google doc](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview). ## [Why specify a package format?](https://angular.dev/#why-specify-a-package-format) In today's JavaScript landscape, developers consume packages in many different ways, using many different toolchains (webpack, Rollup, esbuild, etc.). These tools may understand and require different inputs - some tools may be able to process the latest ES language version, while others may benefit from directly consuming an older ES version. The Angular distribution format supports all of the commonly used development tools and workflows, and adds emphasis on optimizations that result either in smaller application payload size or faster development iteration cycle (build time). Developers can rely on Angular CLI and [ng-packagr](https://github.com/ng-packagr/ng-packagr) (a build tool Angular CLI uses) to produce packages in the Angular package format. See the [Creating Libraries](https://angular.dev/tools/libraries/creating-libraries) guide for more details. ## [File layout](https://angular.dev/#file-layout) The following example shows a simplified version of the `@angular/core` package's file layout, with an explanation for each file in the package. node_modules/@angular/core├── README.md├── package.json├── index.d.ts├── fesm2022│ ├── core.mjs│ ├── core.mjs.map│ ├── testing.mjs│ └── testing.mjs.map└── testing └── index.d.ts This table describes the file layout under `node_modules/@angular/core` annotated to describe the purpose of files and directories: | Files | Purpose | | --- | --- | | `README.md` | Package README, used by npmjs web UI. | | `package.json` | Primary `package.json`, describing the package itself as well as all available entrypoints and code formats. This file contains the "exports" mapping used by runtimes and tools to perform module resolution. | | `index.d.ts` | Bundled `.d.ts` for the primary entrypoint `@angular/core`. | | `fesm2022/` ─ `core.mjs` ─ `core.mjs.map` ─ `testing.mjs` ─ `testing.mjs.map` | Code for all entrypoints in flattened (FESM) ES2022 format, along with source maps. | | `testing/` | Directory representing the "testing" entrypoint. | | `testing/index.d.ts` | Bundled `.d.ts` for the `@angular/core/testing` entrypoint. | ## [`package.json`](https://angular.dev/#packagejson) The primary `package.json` contains important package metadata, including the following: * It [declares](https://angular.dev/#esm-declaration) the package to be in EcmaScript Module (ESM) format * It contains an [`"exports"` field](https://angular.dev/#exports) which defines the available source code formats of all entrypoints * It contains [keys](https://angular.dev/#legacy-resolution-keys) which define the available source code formats of the primary `@angular/core` entrypoint, for tools which do not understand `"exports"`. These keys are considered deprecated, and could be removed as the support for `"exports"` rolls out across the ecosystem. * It declares whether the package contains [side effects](https://angular.dev/#side-effects) ### [ESM declaration](https://angular.dev/#esm-declaration) The top-level `package.json` contains the key: { "type": "module"} This informs resolvers that code within the package is using EcmaScript Modules as opposed to CommonJS modules. ### [`"exports"`](https://angular.dev/#quotexportsquot) The `"exports"` field has the following structure: "exports": { "./schematics/*": { "default": "./schematics/*.js" }, "./package.json": { "default": "./package.json" }, ".": { "types": "./core.d.ts", "default": "./fesm2022/core.mjs" }, "./testing": { "types": "./testing/testing.d.ts", "default": "./fesm2022/testing.mjs" }} Of primary interest are the `"."` and the `"./testing"` keys, which define the available code formats for the `@angular/core` primary entrypoint and the `@angular/core/testing` secondary entrypoint, respectively. For each entrypoint, the available formats are: | Formats | Details | | --- | --- | | Typings (`.d.ts` files) | `.d.ts` files are used by TypeScript when depending on a given package. | | `default` | ES2022 code flattened into a single source. | Tooling that is aware of these keys may preferentially select a desirable code format from `"exports"`. Libraries may want to expose additional static files which are not captured by the exports of the JavaScript-based entry-points such as Sass mixins or pre-compiled CSS. For more information, see [Managing assets in a library](https://angular.dev/tools/libraries/creating-libraries#managing-assets-in-a-library). ### [Legacy resolution keys](https://angular.dev/#legacy-resolution-keys) In addition to `"exports"`, the top-level `package.json` also defines legacy module resolution keys for resolvers that don't support `"exports"`. For `@angular/core` these are: { "module": "./fesm2022/core.mjs", "typings": "./core.d.ts",} As shown in the preceding code snippet, a module resolver can use these keys to load a specific code format. ### [Side effects](https://angular.dev/#side-effects) The last function of `package.json` is to declare whether the package has [side effects](https://angular.dev/#sideeffects-flag). { "sideEffects": false} Most Angular packages should not depend on top-level side effects, and thus should include this declaration. ## [Entrypoints and code splitting](https://angular.dev/#entrypoints-and-code-splitting) Packages in the Angular Package Format contain one primary entrypoint and zero or more secondary entrypoints (for example, `@angular/common/http`). Entrypoints serve several functions. 1. They define the module specifiers from which users import code (for example, `@angular/core` and `@angular/core/testing`). Users typically perceive these entrypoints as distinct groups of symbols, with different purposes or capability. Specific entrypoints might only be used for special purposes, such as testing. Such APIs can be separated out from the primary entrypoint to reduce the chance of them being used accidentally or incorrectly. 2. They define the granularity at which code can be lazily loaded. Many modern build tools are only capable of "code splitting" (aka lazy loading) at the ES Module level. The Angular Package Format uses primarily a single "flat" ES Module per entry point. This means that most build tooling is not able to split code with a single entry point into multiple output chunks. The general rule for APF packages is to use entrypoints for the smallest sets of logically connected code possible. For example, the Angular Material package publishes each logical component or set of components as a separate entrypoint - one for Button, one for Tabs, etc. This allows each Material component to be lazily loaded separately, if desired. Not all libraries require such granularity. Most libraries with a single logical purpose should be published as a single entrypoint. `@angular/core` for example uses a single entrypoint for the runtime, because the Angular runtime is generally used as a single entity. ### [Resolution of secondary entry points](https://angular.dev/#resolution-of-secondary-entry-points) Secondary entrypoints can be resolved via the `"exports"` field of the `package.json` for the package. ## [README.md](https://angular.dev/#readmemd) The README file in the Markdown format that is used to display description of a package on npm and GitHub. Example README content of @angular/core package: Angular=======The sources for this package are in the main [Angular](https://github.com/angular/angular) repo.Please file issues and pull requests against that repo.License: MIT ## [Partial compilation](https://angular.dev/#partial-compilation) Libraries in the Angular Package Format must be published in "partial compilation" mode. This is a compilation mode for `ngc` which produces compiled Angular code that is not tied to a specific Angular runtime version, in contrast to the full compilation used for applications, where the Angular compiler and runtime versions must match exactly. To partially compile Angular code, use the `compilationMode` flag in the `angularCompilerOptions` property of your `tsconfig.json`: { … "angularCompilerOptions": { "compilationMode": "partial", }} Partially compiled library code is then converted to fully compiled code during the application build process by the Angular CLI. If your build pipeline does not use the Angular CLI then refer to the [Consuming partial ivy code outside the Angular CLI](https://angular.dev/tools/libraries/creating-libraries#consuming-partial-ivy-code-outside-the-angular-cli) guide. ## [Optimizations](https://angular.dev/#optimizations) ### [Flattening of ES modules](https://angular.dev/#flattening-of-es-modules) The Angular Package Format specifies that code be published in "flattened" ES module format. This significantly reduces the build time of Angular applications as well as download and parse time of the final application bundle. Please check out the excellent post ["The cost of small modules"](https://nolanlawson.com/2016/08/15/the-cost-of-small-modules) by Nolan Lawson. The Angular compiler can generate index ES module files. Tools like Rollup can use these files to generate flattened modules in a _Flattened ES Module_ (FESM) file format. FESM is a file format created by flattening all ES Modules accessible from an entrypoint into a single ES Module. It's formed by following all imports from a package and copying that code into a single file while preserving all public ES exports and removing all private imports. The abbreviated name, FESM, pronounced _phe-som_, can be followed by a number such as FESM2020. The number refers to the language level of the JavaScript inside the module. Accordingly a FESM2022 file would be ESM+ES2022 and include import/export statements and ES2022 source code. To generate a flattened ES Module index file, use the following configuration options in your tsconfig.json file: { "compilerOptions": { … "module": "esnext", "target": "es2022", … }, "angularCompilerOptions": { … "flatModuleOutFile": "my-ui-lib.js", "flatModuleId": "my-ui-lib" }} Once the index file (for example, `my-ui-lib.js`) is generated by ngc, bundlers and optimizers like Rollup can be used to produce the flattened ESM file. ### ["sideEffects" flag](https://angular.dev/#sideeffects-flag) By default, EcmaScript Modules are side-effectful: importing from a module ensures that any code at the top level of that module should run. This is often undesirable, as most side-effectful code in typical modules is not truly side-effectful, but instead only affects specific symbols. If those symbols are not imported and used, it's often desirable to remove them in an optimization process known as tree-shaking, and the side-effectful code can prevent this. Build tools such as webpack support a flag which allows packages to declare that they do not depend on side-effectful code at the top level of their modules, giving the tools more freedom to tree-shake code from the package. The end result of these optimizations should be smaller bundle size and better code distribution in bundle chunks after code-splitting. This optimization can break your code if it contains non-local side-effects - this is however not common in Angular applications and it's usually a sign of bad design. The recommendation is for all packages to claim the side-effect free status by setting the `sideEffects` property to `false`, and that developers follow the [Angular Style Guide](https://angular.dev/style-guide) which naturally results in code without non-local side-effects. More info: [webpack docs on side effects](https://github.com/webpack/webpack/tree/master/examples/side-effects) ### [ES2022 language level](https://angular.dev/#es2022-language-level) ES2022 Language level is now the default language level that is consumed by Angular CLI and other tooling. The Angular CLI down-levels the bundle to a language level that is supported by all targeted browsers at application build time. ### [d.ts bundling / type definition flattening](https://angular.dev/#dts-bundling---type-definition-flattening) As of APF v8, it is recommended to bundle TypeScript definitions. Bundling of type definitions can significantly speed up compilations for users, especially if there are many individual `.ts` source files in your library. Angular uses [`rollup-plugin-dts`](https://github.com/Swatinem/rollup-plugin-dts) to flatten `.d.ts` files (using `rollup`, similar to how FESM files are created). Using rollup for `.d.ts` bundling is beneficial as it supports code splitting between entry-points. For example, consider you have multiple entrypoints relying on the same shared type, a shared `.d.ts` file would be created along with the larger flattened `.d.ts` files. This is desirable and avoids duplication of types. ### [Tslib](https://angular.dev/#tslib) As of APF v10, it is recommended to add tslib as a direct dependency of your primary entry-point. This is because the tslib version is tied to the TypeScript version used to compile your library. ## [Examples](https://angular.dev/#examples) [@angular/core package](https://unpkg.com/browse/@angular/core@17.0.0/) [@angular/material package](https://unpkg.com/browse/@angular/material@17.0.0/) ## [Definition of terms](https://angular.dev/#definition-of-terms) The following terms are used throughout this document intentionally. In this section are the definitions of all of them to provide additional clarity. ### [Package](https://angular.dev/#package) The smallest set of files that are published to NPM and installed together, for example `@angular/core`. This package includes a manifest called package.json, compiled source code, typescript definition files, source maps, metadata, etc. The package is installed with `npm install @angular/core`. ### [Symbol](https://angular.dev/#symbol) A class, function, constant, or variable contained in a module and optionally made visible to the external world via a module export. ### [Module](https://angular.dev/#module) Short for ECMAScript Modules. A file containing statements that import and export symbols. This is identical to the definition of modules in the ECMAScript spec. ### [ESM](https://angular.dev/#esm) Short for ECMAScript Modules (see above). ### [FESM](https://angular.dev/#fesm) Short for Flattened ES Modules and consists of a file format created by flattening all ES Modules accessible from an entry point into a single ES Module. ### [Module ID](https://angular.dev/#module-id) The identifier of a module used in the import statements (for example, `@angular/core`). The ID often maps directly to a path on the filesystem, but this is not always the case due to various module resolution strategies. ### [Module specifier](https://angular.dev/#module-specifier) A module identifier (see above). ### [Module resolution strategy](https://angular.dev/#module-resolution-strategy) Algorithm used to convert Module IDs to paths on the filesystem. Node.js has one that is well specified and widely used, TypeScript supports several module resolution strategies, [Closure Compiler](https://developers.google.com/closure/compiler) has yet another strategy. ### [Module format](https://angular.dev/#module-format) Specification of the module syntax that covers at minimum the syntax for the importing and exporting from a file. Common module formats are CommonJS (CJS, typically used for Node.js applications) or ECMAScript Modules (ESM). The module format indicates only the packaging of the individual modules, but not the JavaScript language features used to make up the module content. Because of this, the Angular team often uses the language level specifier as a suffix to the module format, (for example, ESM+ES2022 specifies that the module is in ESM format and contains ES2022 code). ### [Bundle](https://angular.dev/#bundle) An artifact in the form of a single JS file, produced by a build tool (for example, [webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/)) that contains symbols originating in one or more modules. Bundles are a browser-specific workaround that reduce network strain that would be caused if browsers were to start downloading hundreds if not tens of thousands of files. Node.js typically doesn't use bundles. Common bundle formats are UMD and System.register. ### [Language level](https://angular.dev/#language-level) The language of the code (ES2022). Independent of the module format. ### [Entry point](https://angular.dev/#entry-point) A module intended to be imported by the user. It is referenced by a unique module ID and exports the public API referenced by that module ID. An example is `@angular/core` or `@angular/core/testing`. Both entry points exist in the `@angular/core` package, but they export different symbols. A package can have many entry points. ### [Deep import](https://angular.dev/#deep-import) A process of retrieving symbols from modules that are not Entry Points. These module IDs are usually considered to be private APIs that can change over the lifetime of the project or while the bundle for the given package is being created. ### [Top-Level import](https://angular.dev/#top-level-import) An import coming from an entry point. The available top-level imports are what define the public API and are exposed in "@angular/name" modules, such as `@angular/core` or `@angular/common`. ### [Tree-shaking](https://angular.dev/#tree-shaking) The process of identifying and removing code not used by an application - also known as dead code elimination. This is a global optimization performed at the application level using tools like [Rollup](https://rollupjs.org/), [Closure Compiler](https://developers.google.com/closure/compiler), or [Terser](https://github.com/terser/terser). ### [AOT compiler](https://angular.dev/#aot-compiler) The Ahead of Time Compiler for Angular. ### [Flattened type definitions](https://angular.dev/#flattened-type-definitions) The bundled TypeScript definitions generated from [API Extractor](https://api-extractor.com/). --- ## Page: https://angular.dev/tools/libraries/tools/libraries/creating-libraries#publishing-your-library ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/reference/configs/file-structure#library-project-files ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/tools/cli/aot-compiler ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/guide/di/lightweight-injection-tokens ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/tools/cli/schematics ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/tools/cli/schematics-for-libraries ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/tools/libraries/angular-package-format#quotexportsquot ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/reference/configs/npm-packages ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/cli/update ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/libraries/reference/configs/workspace-config ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/tools/devtools Angular DevTools is a browser extension that provides debugging and profiling capabilities for Angular applications. Install Angular DevTools from the [Chrome Web Store](https://chrome.google.com/webstore/detail/angular-developer-tools/ienfalfjdbdpebioblfackkekamfmbnh) or from [Firefox Addons](https://addons.mozilla.org/firefox/addon/angular-devtools/). You can open Chrome or Firefox DevTools on any web page by pressing F12 or Ctrl+Shift+I (Windows or Linux) and Fn+F12 or Cmd+Option+I (Mac). Once browser DevTools is open and Angular DevTools is installed, you can find it under the "Angular" tab. **HELPFUL:** Chrome's new tab page does not run installed extensions, so the Angular tab will not appear in DevTools. Visit any other page to see it.  ## [Open your application](https://angular.dev/#open-your-application) When you open the extension, you'll see two additional tabs: | Tabs | Details | | --- | --- | | [Components](https://angular.dev/tools/devtools#debug-your-application) | Lets you explore the components and directives in your application and preview or edit their state. | | [Profiler](https://angular.dev/tools/devtools#profile-your-application) | Lets you profile your application and understand what the performance bottleneck is during change detection execution. |  In the top-right corner of Angular DevTools you'll find which version of Angular is running on the page as well as the latest commit hash for the extension. ### [Angular application not detected](https://angular.dev/#angular-application-not-detected) If you see an error message "Angular application not detected" when opening Angular DevTools, this means it is not able to communicate with an Angular app on the page. The most common reason for this is because the web page you are inspecting does not contain an Angular application. Double check that you are inspecting the right web page and that the Angular app is running. ### [We detected an application built with production configuration](https://angular.dev/#we-detected-an-application-built-with-production-configuration) If you see an error message "We detected an application built with production configuration. Angular DevTools only supports development builds.", this means that an Angular application was found on the page, but it was compiled with production optimizations. When compiling for production, Angular CLI removes various debug features to minimize the amount of the JavaScript on the page to improve performance. This includes features needed to communicate with DevTools. To run DevTools, you need to compile your application with optimizations disabled. `ng serve` does this by default. If you need to debug a deployed application, disable optimizations in your build with the [`optimization` configuration option](https://angular.dev/reference/configs/workspace-config#optimization-configuration) (`{"optimization": false}`). ## [Debug your application](https://angular.dev/#debug-your-application) The **Components** tab lets you explore the structure of your application. You can visualize the component and directive instances in the DOM and inspect or modify their state. ### [Explore the application structure](https://angular.dev/#explore-the-application-structure) The component tree displays a hierarchical relationship of the _components and directives_ within your application.  Click the individual components or directives in the component explorer to select them and preview their properties. Angular DevTools displays properties and metadata on the right side of the component tree. To look up a component or directive by name use the search box above the component tree.  ### [Navigate to the host node](https://angular.dev/#navigate-to-the-host-node) To go to the host element of a particular component or directive, double-click it in the component explorer. Angular DevTools will open the Elements tab in Chrome or the Inspector tab in Firefox, and select the associated DOM node. ### [Navigate to source](https://angular.dev/#navigate-to-source) For components, Angular DevTools lets you navigate to the component definition in the Sources tab (Chrome) and Debugger tab (Firefox). After you select a particular component, click the icon at the top-right of the properties view:  ### [Update property value](https://angular.dev/#update-property-value) Like browsers' DevTools, the properties view lets you edit the value of an input, output, or other properties. Right-click on the property value and if edit functionality is available for this value type, a text input will appear. Type the new value and press `Enter` to apply this value to the property.  ### [Access selected component or directive in console](https://angular.dev/#access-selected-component-or-directive-in-console) As a shortcut in the console, Angular DevTools provides access to instances of recently selected components or directives. Type `$ng0` to get a reference to the instance of the currently selected component or directive, and type `$ng1` for the previously selected instance, `$ng2` for the instance selected before that, and so on.  ### [Select a directive or component](https://angular.dev/#select-a-directive-or-component) Similar to browsers' DevTools, you can inspect the page to select a particular component or directive. Click the _**Inspect element**_ icon in the top left corner within Angular DevTools and hover over a DOM element on the page. The extension recognizes the associated directives and/or components and lets you select the corresponding element in the Component tree.  ## [Profile your application](https://angular.dev/#profile-your-application) The **Profiler** tab lets you visualize the execution of Angular's change detection. This is useful for determining when and how change detection impacts your application's performance.  The Profiler tab lets you start profiling the current application or import an existing profile from a previous run. To start profiling your application, hover over the circle in the top-left corner within the **Profiler** tab and click **Start recording**. During profiling, Angular DevTools captures execution events, such as change detection and lifecycle hook execution. Interact with your application to trigger change detection and generate data Angular DevTools can use. To finish recording, click the circle again to **Stop recording**. You can also import an existing recording. Read more about this feature in the [Import recording](https://angular.dev/tools/devtools#import-and-export-recordings) section. ### [Understand your application's execution](https://angular.dev/#understand-your-applications-execution) After recording or importing a profile, Angular DevTools displays a visualization of change detection cycles.  Each bar in the sequence represents a change detection cycle in your app. The taller a bar is, the longer the application spent running change detection in this cycle. When you select a bar, DevTools displays useful information about it including: * A bar chart with all the components and directives that it captured during this cycle * How much time Angular spent running change detection in this cycle. * An estimated frame rate as experienced by the user. * The source which triggered change detection.  ### [Understand component execution](https://angular.dev/#understand-component-execution) The bar chart displayed after clicking on a change detection cycle displays a detailed view about how much time your application spent running change detection in that particular component or directive. This example shows the total time spent by the `NgForOf` directive and which method was called on it.  ### [Hierarchical views](https://angular.dev/#hierarchical-views) ![A screenshot of the 'Profiler' tab. A single bar has been selected by the user and a nearby dropdown menu now displays 'Flame graph', showing a flame graph underneath it. The flame graph starts with a row called 'Entire application' and another row called 'AppComponent'. Beneath those, the rows start to break up into multiple items, starting with `[RouterOutlet]` and `DemoAppComponent` on the third row. A few layers deep, one cell is highlighted red.](https://angular.dev/assets/images/guide/devtools/flame-graph-view.png) You can also visualize the change detection execution in a flame graph-like view. Each tile in the graph represents an element on the screen at a specific position in the render tree. For example, consider a change detection cycle where a `LoggedOutUserComponent` is removed and in its place Angular rendered a `LoggedInUserComponent`. In this scenario both components will be displayed in the same tile. The x-axis represents the full time it took to render this change detection cycle. The y-axis represents the element hierarchy. Running change detection for an element requires render its directives and child components. Together, this graph visualizes which components are taking the longest time to render and where that time is going. Each tile is colored depending on how much time Angular spent there. Angular DevTools determines the intensity of the color by the time spent relative to the tile where rendering took the most time. When you click on a certain tile, you'll see details about it in the panel on the right. Double-clicking the tile zooms it in so you can more easily view its nested children. ### [Debug change detection and `OnPush` components](https://angular.dev/#debug-change-detection-and-onpush-components) Normally, the graph visualizes the time it takes to _render_ an application, for any given change detection frame. However some components such as `OnPush` components will only re-render if their input properties change. It can be useful to visualize the flame graph without these components for particular frames. To visualize only the components in a change detection frame that went through the change detection process, select the **Change detection** checkbox at the top, above the flame graph. This view highlights all the components that went through change detection and displays those that did not in gray, such as `OnPush` components that did not re-render. ![A screenshot of the 'Profiler' tab displaying a flame chart visualization of a change detection cycle. A checkbox labeled 'Show only change detection' is now checked. The flame graph looks very similar to before, however the color of components has changed from orange to blue. Several tiles labeled `[RouterOutlet]` are no longer highlighted with any color.](https://angular.dev/assets/images/guide/devtools/debugging-onpush.png) ### [Import and export recordings](https://angular.dev/#import-and-export-recordings) Click the **Save Profile** button at the top-right of a recorded profiling session to export it as a JSON file and save it to the disk. Later, import the file in the initial view of the profiler by clicking the **Choose file** input.  ## [Inspect your injectors](https://angular.dev/#inspect-your-injectors) NOTE: The Injector Tree is available for Angular Applications built with version 17 or higher. ### [View the injector hierarchy of your application](https://angular.dev/#view-the-injector-hierarchy-of-your-application) The **Injector Tree** tab lets you explore the structure of the Injectors configured in your application. Here you will see two trees representing the [injector hierarchy](https://angular.dev/guide/di/hierarchical-dependency-injection) of your application. One tree is your environment hierarchy, the other is your element hierarchy.  ### [Visualize resolution paths](https://angular.dev/#visualize-resolution-paths) When a specific injector is selected, the path that Angular's dependency injection algorithm traverses from that injector to the root is highlighted. For element injectors, this includes highlighting the environment injectors that the dependency injection algorithm jumps to when a dependency cannot be resolved in the element hierarchy. See [resolution rules](https://angular.dev/guide/di/hierarchical-dependency-injection#resolution-rules) for more details about how Angular resolves resolution paths.  ### [View injector providers](https://angular.dev/#view-injector-providers) Clicking an injector that has configured providers will display those providers in a list on the right of the injector tree view. Here you can view the provided token and it's type.  --- ## Page: https://angular.dev/tools/language-service The Angular Language Service provides code editors with a way to get completions, errors, hints, and navigation inside Angular templates. It works with external templates in separate HTML files, and also with in-line templates. ## [Configuring compiler options for the Angular Language Service](https://angular.dev/#configuring-compiler-options-for-the-angular-language-service) To enable the latest Language Service features, set the `strictTemplates` option in `tsconfig.json` by setting `strictTemplates` to `true`, as shown in the following example: "angularCompilerOptions": { "strictTemplates": true} For more information, see the [Angular compiler options](https://angular.dev/reference/configs/angular-compiler-options) guide. ## [Features](https://angular.dev/#features) Your editor autodetects that you are opening an Angular file. It then uses the Angular Language Service to read your `tsconfig.json` file, find all the templates you have in your application, and then provide language services for any templates that you open. Language services include: * Completions lists * AOT Diagnostic messages * Quick info * Go to definition ### [Autocompletion](https://angular.dev/#autocompletion) Autocompletion can speed up your development time by providing you with contextual possibilities and hints as you type. This example shows autocomplete in an interpolation. As you type it out, you can press tab to complete.  There are also completions within elements. Any elements you have as a component selector will show up in the completion list. ### [Error checking](https://angular.dev/#error-checking) The Angular Language Service can forewarn you of mistakes in your code. In this example, Angular doesn't know what `orders` is or where it comes from.  ### [Quick info and navigation](https://angular.dev/#quick-info-and-navigation) The quick-info feature lets you hover to see where components, directives, and modules come from. You can then click "Go to definition" or press F12 to go directly to the definition.  ## [Angular Language Service in your editor](https://angular.dev/#angular-language-service-in-your-editor) Angular Language Service is currently available as an extension for [Visual Studio Code](https://code.visualstudio.com/), [WebStorm](https://www.jetbrains.com/webstorm), [Sublime Text](https://www.sublimetext.com/) and [Eclipse IDE](https://www.eclipse.org/eclipseide). ### [Visual Studio Code](https://angular.dev/#visual-studio-code) In [Visual Studio Code](https://code.visualstudio.com/), install the extension from the [Extensions: Marketplace](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template). Open the marketplace from the editor using the Extensions icon on the left menu pane, or use VS Quick Open (⌘+P on Mac, CTRL+P on Windows) and type "? ext". In the marketplace, search for Angular Language Service extension, and click the **Install** button. The Visual Studio Code integration with the Angular language service is maintained and distributed by the Angular team. ### [Visual Studio](https://angular.dev/#visual-studio) In [Visual Studio](https://visualstudio.microsoft.com/), install the extension from the [Extensions: Marketplace](https://marketplace.visualstudio.com/items?itemName=TypeScriptTeam.AngularLanguageService). Open the marketplace from the editor selecting Extensions on the top menu pane, and then selecting Manage Extensions. In the marketplace, search for Angular Language Service extension, and click the **Install** button. The Visual Studio integration with the Angular language service is maintained and distributed by Microsoft with help from the Angular team. Check out the project [here](https://github.com/microsoft/vs-ng-language-service). ### [WebStorm](https://angular.dev/#webstorm) In [WebStorm](https://www.jetbrains.com/webstorm), enable the plugin [Angular and AngularJS](https://plugins.jetbrains.com/plugin/6971-angular-and-angularjs). Since WebStorm 2019.1, the `@angular/language-service` is not required anymore and should be removed from your `package.json`. ### [Sublime Text](https://angular.dev/#sublime-text) In [Sublime Text](https://www.sublimetext.com/), the Language Service supports only in-line templates when installed as a plug-in. You need a custom Sublime plug-in (or modifications to the current plug-in) for completions in HTML files. To use the Language Service for in-line templates, you must first add an extension to allow TypeScript, then install the Angular Language Service plug-in. Starting with TypeScript 2.3, TypeScript has a plug-in model that the language service can use. 1. Install the latest version of TypeScript in a local `node_modules` directory: npm install --save-dev typescript 2. Install the Angular Language Service package in the same location: npm install --save-dev @angular/language-service 3. Once the package is installed, add the following to the `"compilerOptions"` section of your project's `tsconfig.json`. "plugins": [ {"name": "@angular/language-service"} ] 4. In your editor's user preferences (`Cmd+,` or `Ctrl+,`), add the following: "typescript-tsdk": "<path to your folder>/node_modules/typescript/lib" This lets the Angular Language Service provide diagnostics and completions in `.ts` files. ### [Eclipse IDE](https://angular.dev/#eclipse-ide) Either directly install the "Eclipse IDE for Web and JavaScript developers" package which comes with the Angular Language Server included, or from other Eclipse IDE packages, use Help > Eclipse Marketplace to find and install [Eclipse Wild Web Developer](https://marketplace.eclipse.org/content/wild-web-developer-html-css-javascript-typescript-nodejs-angular-json-yaml-kubernetes-xml). ### [Neovim](https://angular.dev/#neovim) #### [Conquer of Completion with Node.js](https://angular.dev/#conquer-of-completion-with-nodejs) The Angular Language Service uses the tsserver, which doesn't follow the LSP specifications exactly. Therefore if you are using neovim or vim with JavaScript or TypeScript or Angular you may find that [Conquer of Completion](https://github.com/neoclide/coc.nvim) (COC) has the fullest implementation of the Angular Language Service and the tsserver. This is because COC ports the VSCode implementation of the tsserver which accommodates the tsserver's implementation. 1. [Setup coc.nvim](https://github.com/neoclide/coc.nvim) 2. Configure the Angular Language Service Once installed run the `CocConfig` vim command line command to open the config file `coc-settings.json` and add the angular property. Make sure to substitute the correct paths to your global `node_modules` such that they go to directories which contain `tsserver` and the `ngserver` respectively. { "languageserver": { "angular": { "command": "ngserver", "args": [ "--stdio", "--tsProbeLocations", "/usr/local/lib/node_modules/typescript/lib/CHANGE/THIS/TO/YOUR/GLOBAL/NODE_MODULES", "--ngProbeLocations", "/usr/local/lib/node_modules/@angular/language-server/bin/CHANGE/THIS/TO/YOUR/GLOBAL/NODE_MODULES" ], "filetypes": ["ts", "typescript", "html"], "trace.server.verbosity": "verbose" } } } **HELPFUL:** `/usr/local/lib/node_modules/typescript/lib` and `/usr/local/lib/node_modules/@angular/language-server/bin` above should point to the location of your global node modules, which may be different. #### [Built In Neovim LSP](https://angular.dev/#built-in-neovim-lsp) Angular Language Service can be used with Neovim by using the [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) plugin. 1. [Install nvim-lspconfig](https://github.com/neovim/nvim-lspconfig?tab=readme-ov-file#install) 2. [Configure angularls for nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#angularls) ## [How the Language Service works](https://angular.dev/#how-the-language-service-works) When you use an editor with a language service, the editor starts a separate language-service process and communicates with it through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call), using the [Language Server Protocol](https://microsoft.github.io/language-server-protocol). When you type into the editor, the editor sends information to the language-service process to track the state of your project. When you trigger a completion list within a template, the editor first parses the template into an HTML [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree). The Angular compiler interprets that tree to determine the context: which module the template is part of, the current scope, the component selector, and where your cursor is in the template AST. It can then determine the symbols that could potentially be at that position. It's a little more involved if you are in an interpolation. If you have an interpolation of `{{data.---}}` inside a `div` and need the completion list after `data.---`, the compiler can't use the HTML AST to find the answer. The HTML AST can only tell the compiler that there is some text with the characters "`{{data.---}}`". That's when the template parser produces an expression AST, which resides within the template AST. The Angular Language Services then looks at `data.---` within its context, asks the TypeScript Language Service what the members of `data` are, and returns the list of possibilities. ## [More information](https://angular.dev/#more-information) * For more in-depth information on the implementation, see the [Angular Language Service source](https://github.com/angular/angular/blob/main/packages/language-service/src) * For more on the design considerations and intentions, see [design documentation here](https://github.com/angular/vscode-ng-language-service/wiki/Design) --- ## Page: https://angular.dev/style-guide Looking for an opinionated guide to Angular syntax, conventions, and application structure? Step right in. This style guide presents preferred conventions and, as importantly, explains why. ## [Style vocabulary](https://angular.dev/#style-vocabulary) Each guideline describes either a good or bad practice, and all have a consistent presentation. The wording of each guideline indicates how strong the recommendation is. **Do** is one that should always be followed. _Always_ might be a bit too strong of a word. Guidelines that literally should always be followed are extremely rare. On the other hand, you need a really unusual case for breaking a _Do_ guideline. **Consider** guidelines should generally be followed. If you fully understand the meaning behind the guideline and have a good reason to deviate, then do so. Aim to be consistent. **Avoid** indicates something you should almost never do. Code examples to _avoid_ have an unmistakable red header. **Why**? Gives reasons for following the previous recommendations. ## [File structure conventions](https://angular.dev/#file-structure-conventions) Some code examples display a file that has one or more similarly named companion files. For example, `hero.component.ts` and `hero.component.html`. The guideline uses the shortcut `hero.component.ts|html|css|spec` to represent those various files. Using this shortcut makes this guide's file structures easier to read and more terse. ## [Single responsibility](https://angular.dev/#single-responsibility) Apply the [_single responsibility principle (SRP)_](https://wikipedia.org/wiki/Single_responsibility_principle) to all components, services, and other symbols. This helps make the application cleaner, easier to read and maintain, and more testable. ### [Rule of One](https://angular.dev/#rule-of-one) #### [Style 01-01](https://angular.dev/#style-01-01) **Do** define one thing, such as a service or component, per file. **Consider** limiting files to 400 lines of code. **Why**? One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control. **Why**? One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies. **Why**? A single component can be the default export for its file which facilitates lazy loading with the router. The key is to make the code more reusable, easier to read, and less mistake-prone. The following _negative_ example defines the `AppComponent`, bootstraps the app, defines the `Hero` model object, and loads heroes from the server all in the same file. _Don't do this_. /* avoid */import {Component, NgModule, OnInit} from '@angular/core';import {BrowserModule} from '@angular/platform-browser';import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';interface Hero { id: number; name: string;}@Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <pre>{{heroes | json}}</pre> `, styleUrls: ['../app.component.css'], standalone: false,})export class AppComponent implements OnInit { title = 'Tour of Heroes'; heroes: Hero[] = []; ngOnInit() { getHeroes().then((heroes) => (this.heroes = heroes)); }}@NgModule({ imports: [BrowserModule], declarations: [AppComponent], exports: [AppComponent], bootstrap: [AppComponent],})export class AppModule {}platformBrowserDynamic().bootstrapModule(AppModule);const HEROES: Hero[] = [ {id: 1, name: 'Bombasto'}, {id: 2, name: 'Tornado'}, {id: 3, name: 'Magneta'},];function getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); // TODO: get hero data from the server;} It is a better practice to redistribute the component and its supporting classes into their own, dedicated files. import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';import {AppModule} from './app/app.module';platformBrowserDynamic().bootstrapModule(AppModule); import {NgModule} from '@angular/core';import {BrowserModule} from '@angular/platform-browser';import {RouterModule} from '@angular/router';import {AppComponent} from './app.component';import {HeroesComponent} from './heroes/heroes.component';@NgModule({ imports: [ BrowserModule, RouterModule.forChild([{path: '01-01', component: AppComponent}]), ], declarations: [AppComponent, HeroesComponent], exports: [AppComponent], bootstrap: [AppComponent],})export class AppModule {} import {Component} from '@angular/core';import {HeroService} from './heroes';@Component({ selector: 'toh-app', template: ` <toh-heroes></toh-heroes> `, styleUrls: ['./app.component.css'], providers: [HeroService], standalone: false,})export class AppComponent {} import {Component, inject, OnInit} from '@angular/core';import {Hero, HeroService} from './shared';@Component({ selector: 'toh-heroes', template: ` <pre>{{heroes | json}}</pre> `, standalone: false,})export class HeroesComponent { heroes: Hero[] = []; private heroService = inject(HeroService); constructor() { this.heroService.getHeroes().then((heroes) => (this.heroes = heroes)); }} import {Injectable} from '@angular/core';import {HEROES} from './mock-heroes';@Injectable()export class HeroService { getHeroes() { return Promise.resolve(HEROES); }} export interface Hero { id: number; name: string;} import {Hero} from './hero.model';export const HEROES: Hero[] = [ {id: 1, name: 'Bombasto'}, {id: 2, name: 'Tornado'}, {id: 3, name: 'Magneta'},]; As the application grows, this rule becomes even more important. ## [Naming](https://angular.dev/#naming) Naming conventions are hugely important to maintainability and readability. This guide recommends naming conventions for the file name and the symbol name. ### [General Naming Guidelines](https://angular.dev/#general-naming-guidelines) #### [Style 02-01](https://angular.dev/#style-02-01) **Do** use consistent names for all symbols. **Do** follow a pattern that describes the symbol's feature then its type. The recommended pattern is `feature.type.ts`. **Why**? Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency. **Why**? The naming conventions should help find desired code faster and make it easier to understand. **Why**? Names of folders and files should clearly convey their intent. For example, `app/heroes/hero-list.component.ts` may contain a component that manages a list of heroes. ### [Separate file names with dots and dashes](https://angular.dev/#separate-file-names-with-dots-and-dashes) #### [Style 02-02](https://angular.dev/#style-02-02) **Do** use dashes to separate words in the descriptive name. **Do** use dots to separate the descriptive name from the type. **Do** use consistent type names for all components following a pattern that describes the component's feature then its type. A recommended pattern is `feature.type.ts`. **Do** use conventional type names including `.service`, `.component`, `.pipe`, `.module`, and `.directive`. Invent additional type names if you must but take care not to create too many. **Why**? Type names provide a consistent way to quickly identify what is in the file. **Why**? Type names make it easy to find a specific file type using an editor or IDE's fuzzy search techniques. **Why**? Unabbreviated type names such as `.service` are descriptive and unambiguous. Abbreviations such as `.srv`, `.svc`, and `.serv` can be confusing. **Why**? Type names provide pattern matching for any automated tasks. ### [Symbols and file names](https://angular.dev/#symbols-and-file-names) #### [Style 02-03](https://angular.dev/#style-02-03) **Do** use consistent names for all assets named after what they represent. **Do** use upper camel case for class names. **Do** match the name of the symbol to the name of the file. **Do** append the symbol name with the conventional suffix (such as `Component`, `Directive`, `Module`, `Pipe`, or `Service`) for a thing of that type. **Do** give the filename the conventional suffix (such as `.component.ts`, `.directive.ts`, `.module.ts`, `.pipe.ts`, or `.service.ts`) for a file of that type. **Why**? Consistent conventions make it easy to quickly identify and reference assets of different types. | Symbol name | File name | | --- | --- | | @Component({ … }) export class AppComponent { } | app.component.ts | | @Component({ … }) export class HeroesComponent { } | heroes.component.ts | | @Component({ … }) export class HeroListComponent { } | hero-list.component.ts | | @Component({ … }) export class HeroDetailComponent { } | hero-detail.component.ts | | @Directive({ … }) export class ValidationDirective { } | validation.directive.ts | | @NgModule({ … }) export class AppModule | app.module.ts | | @Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { } | init-caps.pipe.ts | | @Injectable() export class UserProfileService { } | user-profile.service.ts | ### [Service names](https://angular.dev/#service-names) #### [Style 02-04](https://angular.dev/#style-02-04) **Do** use consistent names for all services named after their feature. **Do** suffix a service class name with `Service`. For example, something that gets data or heroes should be called a `DataService` or a `HeroService`. A few terms are unambiguously services. They typically indicate agency by ending in "-er". You may prefer to name a service that logs messages `Logger` rather than `LoggerService`. Decide if this exception is agreeable in your project. As always, strive for consistency. **Why**? Provides a consistent way to quickly identify and reference services. **Why**? Clear service names such as `Logger` do not require a suffix. **Why**? Service names such as `Credit` are nouns and require a suffix and should be named with a suffix when it is not obvious if it is a service or something else. | Symbol name | File name | | --- | --- | | @Injectable() export class HeroDataService { } | hero-data.service.ts | | @Injectable() export class CreditService { } | credit.service.ts | | @Injectable() export class Logger { } | logger.service.ts | ### [Bootstrapping](https://angular.dev/#bootstrapping) #### [Style 02-05](https://angular.dev/#style-02-05) **Do** put bootstrapping and platform logic for the application in a file named `main.ts`. **Do** include error handling in the bootstrapping logic. **Avoid** putting application logic in `main.ts`. Instead, consider placing it in a component or service. **Why**? Follows a consistent convention for the startup logic of an app. **Why**? Follows a familiar convention from other technology platforms. import {AppComponent} from './app/app.component';import {bootstrapApplication} from '@angular/platform-browser';bootstrapApplication(AppComponent); ### [Component selectors](https://angular.dev/#component-selectors) #### [Style 05-02](https://angular.dev/#style-05-02) **Do** use _dashed-case_ or _kebab-case_ for naming the element selectors of components. **Why**? Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements). import {Component} from '@angular/core';/* avoid */@Component({ selector: 'tohHeroButton', templateUrl: './hero-button.component.html',})export class HeroButtonComponent {} import {Component} from '@angular/core';@Component({ selector: 'toh-hero-button', templateUrl: './hero-button.component.html',})export class HeroButtonComponent {} <toh-hero-button></toh-hero-button> ### [Component custom prefix](https://angular.dev/#component-custom-prefix) #### [Style 02-07](https://angular.dev/#style-02-07) **Do** use a hyphenated, lowercase element selector value; for example, `admin-users`. **Do** use a prefix that identifies the feature area or the application itself. **Why**? Prevents element name collisions with components in other applications and with native HTML elements. **Why**? Makes it easier to promote and share the component in other applications. **Why**? Components are easy to identify in the DOM. import {Component} from '@angular/core';/* avoid */// HeroComponent is in the Tour of Heroes feature@Component({ selector: 'hero', template: '',})export class HeroComponent {} import {Component} from '@angular/core';/* avoid */// UsersComponent is in an Admin feature@Component({ selector: 'users', template: '',})export class UsersComponent {} import {Component} from '@angular/core';@Component({ template: '<div>hero component</div>', selector: 'toh-hero',})export class HeroComponent {} import {Component} from '@angular/core';@Component({ template: '<div>users component</div>', selector: 'admin-users',})export class UsersComponent {} ### [Directive selectors](https://angular.dev/#directive-selectors) #### [Style 02-06](https://angular.dev/#style-02-06) **Do** Use lower camel case for naming the selectors of directives. **Why**? Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names. **Why**? The Angular HTML parser is case-sensitive and recognizes lower camel case. ### [Directive custom prefix](https://angular.dev/#directive-custom-prefix) #### [Style 02-08](https://angular.dev/#style-02-08) **Do** spell non-element selectors in lower camel case unless the selector is meant to match a native HTML attribute. **Don't** prefix a directive name with `ng` because that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. **Why**? Prevents name collisions. **Why**? Directives are easily identified. import {Directive} from '@angular/core';/* avoid */@Directive({ selector: '[validate]',})export class ValidateDirective {} import {Directive} from '@angular/core';@Directive({ selector: '[tohValidate]',})export class ValidateDirective {} ### [Pipe names](https://angular.dev/#pipe-names) #### [Style 02-09](https://angular.dev/#style-02-09) **Do** use consistent names for all pipes, named after their feature. The pipe class name should use `UpperCamelCase` (the general convention for class names), and the corresponding `name` string should use _lowerCamelCase_. The `name` string cannot use hyphens ("dash-case" or "kebab-case"). **Why**? Provides a consistent way to quickly identify and reference pipes. | Symbol name | File name | | --- | --- | | @Pipe({ name: 'ellipsis' }) export class EllipsisPipe implements PipeTransform { } | ellipsis.pipe.ts | | @Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { } | init-caps.pipe.ts | ### [Unit test file names](https://angular.dev/#unit-test-file-names) #### [Style 02-10](https://angular.dev/#style-02-10) **Do** name test specification files the same as the component they test. **Do** name test specification files with a suffix of `.spec`. **Why**? Provides a consistent way to quickly identify tests. **Why**? Provides pattern matching for [karma](https://karma-runner.github.io/) or other test runners. | Test type | File names | | --- | --- | | Components | heroes.component.spec.ts hero-list.component.spec.ts hero-detail.component.spec.ts | | Services | logger.service.spec.ts hero.service.spec.ts filter-text.service.spec.ts | | Pipes | ellipsis.pipe.spec.ts init-caps.pipe.spec.ts | ## [Application structure and NgModules](https://angular.dev/#application-structure-and-ngmodules) Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the application is heading. All of the application's code goes in a folder named `src`. All feature areas are in their own folder. All content is one asset per file. Each component, service, and pipe is in its own file. All third party vendor scripts are stored in another folder and not in the `src` folder. Use the naming conventions for files in this guide. ### [Overall structural guidelines](https://angular.dev/#overall-structural-guidelines) #### [Style 04-06](https://angular.dev/#style-04-06) **Do** start small but keep in mind where the application is heading down the road. **Do** have a near term view of implementation and a long term vision. **Do** put all of the application's code in a folder named `src`. **Consider** creating a folder for a component when it has multiple accompanying files (`.ts`, `.html`, `.css`, and `.spec`). **Why**? Helps keep the application structure small and easy to maintain in the early stages, while being easy to evolve as the application grows. **Why**? Components often have four files (for example, `*.html`, `*.css`, `*.ts`, and `*.spec.ts`) and can clutter a folder quickly. Here is a compliant folder and file structure: project root├── src│ ├── app│ │ ├── core│ │ │ └── exception.service.ts|spec.ts│ │ │ └── user-profile.service.ts|spec.ts│ │ ├── heroes│ │ │ ├── hero│ │ │ │ └── hero.component.ts|html|css|spec.ts│ │ │ ├── hero-list│ │ │ │ └── hero-list.component.ts|html|css|spec.ts│ │ │ ├── shared│ │ │ │ └── hero-button.component.ts|html|css|spec.ts│ │ │ │ └── hero.model.ts│ │ │ │ └── hero.service.ts|spec.ts│ │ │ └── heroes.component.ts|html|css|spec.ts│ │ │ └── heroes.routes.ts│ │ ├── shared│ │ │ └── init-caps.pipe.ts|spec.ts│ │ │ └── filter-text.component.ts|spec.ts│ │ │ └── filter-text.service.ts|spec.ts│ │ ├── villains│ │ │ ├── villain│ │ │ │ └── …│ │ │ ├── villain-list│ │ │ │ └── …│ │ │ ├── shared│ │ │ │ └── …│ │ │ └── villains.component.ts|html|css|spec.ts│ │ │ └── villains.module.ts│ │ │ └── villains-routing.module.ts│ │ └── app.component.ts|html|css|spec.ts│ │ └── app.routes.ts│ └── main.ts│ └── index.html│ └── …└── node_modules/…└── … **HELPFUL:** While components in dedicated folders are widely preferred, another option for small applications is to keep components flat (not in a dedicated folder). This adds up to four files to the existing folder, but also reduces the folder nesting. Whatever you choose, be consistent. ### [_Folders-by-feature_ structure](https://angular.dev/#folders-by-feature-structure) #### [Style 04-07](https://angular.dev/#style-04-07) **Do** create folders named for the feature area they represent. **Why**? A developer can locate the code and identify what each file represents at a glance. The structure is as flat as it can be and there are no repetitive or redundant names. **Why**? Helps reduce the application from becoming cluttered through organizing the content. **Why**? When there are a lot of files, for example 10+, locating them is easier with a consistent folder structure and more difficult in a flat structure. For more information, refer to [this folder and file structure example](https://angular.dev/#overall-structural-guidelines). ### [App _root module_](https://angular.dev/#app-root-module) **IMPORTANT:** The following style guide recommendations are for applications based on `NgModule`. New applications should use standalone components, directives, and pipes instead. #### [Style 04-08](https://angular.dev/#style-04-08) **Do** create an NgModule in the application's root folder, for example, in `/src/app` if creating a `NgModule` based app. **Why**? Every `NgModule` based application requires at least one root NgModule. **Consider** naming the root module `app.module.ts`. **Why**? Makes it easier to locate and identify the root module. import {NgModule} from '@angular/core';import {BrowserModule} from '@angular/platform-browser';import {RouterModule} from '@angular/router';import {AppComponent} from './app.component';import {HeroesComponent} from './heroes/heroes.component';@NgModule({ imports: [ BrowserModule, RouterModule.forChild([{path: '04-08', component: AppComponent}]), ], declarations: [AppComponent, HeroesComponent], exports: [AppComponent],})export class AppModule {} ### [Feature modules](https://angular.dev/#feature-modules) #### [Style 04-09](https://angular.dev/#style-04-09) **Do** create an NgModule for all distinct features in an application; for example, a `Heroes` feature. **Do** place the feature module in the same named folder as the feature area; for example, in `app/heroes`. **Do** name the feature module file reflecting the name of the feature area and folder; for example, `app/heroes/heroes.module.ts`. **Do** name the feature module symbol reflecting the name of the feature area, folder, and file; for example, `app/heroes/heroes.module.ts` defines `HeroesModule`. **Why**? A feature module can expose or hide its implementation from other modules. **Why**? A feature module identifies distinct sets of related components that comprise the feature area. **Why**? A feature module can easily be routed to both eagerly and lazily. **Why**? A feature module defines clear boundaries between specific functionality and other application features. **Why**? A feature module helps clarify and make it easier to assign development responsibilities to different teams. **Why**? A feature module can easily be isolated for testing. ### [Shared feature module](https://angular.dev/#shared-feature-module) #### [Style 04-10](https://angular.dev/#style-04-10) **Do** create a feature module named `SharedModule` in a `shared` folder; for example, `app/shared/shared.module.ts` defines `SharedModule`. **Do** declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules. **Consider** using the name SharedModule when the contents of a shared module are referenced across the entire application. **Consider** _not_ providing services in shared modules. Services are usually singletons that are provided once for the entire application or in a particular feature module. There are exceptions, however. For example, in the sample code that follows, notice that the `SharedModule` provides `FilterTextService`. This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances. **Do** import all modules required by the assets in the `SharedModule`; for example, `CommonModule` and `FormsModule`. **Why**? `SharedModule` will contain components, directives, and pipes that may need features from another common module; for example, `ngFor` in `CommonModule`. **Do** declare all components, directives, and pipes in the `SharedModule`. **Do** export all symbols from the `SharedModule` that other feature modules need to use. **Why**? `SharedModule` exists to make commonly used components, directives, and pipes available for use in the templates of components in many other modules. **Avoid** specifying app-wide singleton providers in a `SharedModule`. Intentional singletons are OK. Take care. **Why**? A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesirable results. **Why**? You don't want each module to have its own separate instance of singleton services. Yet there is a real danger of that happening if the `SharedModule` provides a service. project root├──src├──├──app├──├──├── shared├──├──├──└── shared.module.ts├──├──├──└── init-caps.pipe.ts|spec.ts├──├──├──└── filter-text.component.ts|spec.ts├──├──├──└── filter-text.service.ts|spec.ts├──├──└── app.component.ts|html|css|spec.ts├──├──└── app.module.ts├──├──└── app-routing.module.ts├──└── main.ts├──└── index.html└── … import {NgModule} from '@angular/core';import {CommonModule} from '@angular/common';import {FormsModule} from '@angular/forms';import {FilterTextComponent} from './filter-text/filter-text.component';import {FilterTextService} from './filter-text/filter-text.service';import {InitCapsPipe} from './init-caps.pipe';@NgModule({ imports: [CommonModule, FormsModule], declarations: [FilterTextComponent, InitCapsPipe], providers: [FilterTextService], exports: [CommonModule, FormsModule, FilterTextComponent, InitCapsPipe],})export class SharedModule {} import {Pipe, PipeTransform} from '@angular/core';@Pipe({ name: 'initCaps', standalone: false,})export class InitCapsPipe implements PipeTransform { transform = (value: string) => value;} import {Component, EventEmitter, Output} from '@angular/core';@Component({ selector: 'toh-filter-text', template: '<input type="text" id="filterText" [(ngModel)]="filter" (keyup)="filterChanged($event)" />', standalone: false,})export class FilterTextComponent { @Output() changed: EventEmitter<string>; filter = ''; constructor() { this.changed = new EventEmitter<string>(); } clear() { this.filter = ''; } filterChanged(event: any) { event.preventDefault(); console.log(`Filter Changed: ${this.filter}`); this.changed.emit(this.filter); }} import {Injectable} from '@angular/core';@Injectable()export class FilterTextService { constructor() { console.log('Created an instance of FilterTextService'); } filter(data: string, props: Array<string>, originalList: Array<any>) { let filteredList: any[]; if (data && props && originalList) { data = data.toLowerCase(); const filtered = originalList.filter((item) => { let match = false; for (const prop of props) { if (item[prop].toString().toLowerCase().indexOf(data) > -1) { match = true; break; } } return match; }); filteredList = filtered; } else { filteredList = originalList; } return filteredList; }} import {Component, inject} from '@angular/core';import {FilterTextService} from '../shared/filter-text/filter-text.service';@Component({ selector: 'toh-heroes', templateUrl: './heroes.component.html', standalone: false,})export class HeroesComponent { heroes = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'Bombasto'}, {id: 3, name: 'Magneta'}, {id: 4, name: 'Tornado'}, ]; filteredHeroes = this.heroes; private filterService = inject(FilterTextService); filterChanged(searchText: string) { this.filteredHeroes = this.filterService.filter(searchText, ['id', 'name'], this.heroes); }} <div>This is heroes component</div><ul> <li *ngFor="let hero of filteredHeroes"> {{ hero.name }} </li></ul><toh-filter-text (changed)="filterChanged($event)"></toh-filter-text> ### [Lazy Loaded folders](https://angular.dev/#lazy-loaded-folders) #### [Style 04-11](https://angular.dev/#style-04-11) A distinct application feature or workflow may be _lazy loaded_ or _loaded on demand_ rather than when the application starts. **Do** put the contents of lazy loaded features in a _lazy loaded folder_. A typical _lazy loaded folder_ contains a _routing component_, its child components, and their related assets. **Why**? The folder makes it easy to identify and isolate the feature content. ## [Components](https://angular.dev/#components) ### [Components as elements](https://angular.dev/#components-as-elements) #### [Style 05-03](https://angular.dev/#style-05-03) **Consider** giving components an _element_ selector, as opposed to _attribute_ or _class_ selectors. **Why**? Components have templates containing HTML and optional Angular template syntax. They display content. Developers place components on the page as they would native HTML elements and web components. **Why**? It is easier to recognize that a symbol is a component by looking at the template's html. **HELPFUL:** There are a few cases where you give a component an attribute, such as when you want to augment a built-in element. For example, [Material Design](https://material.angular.io/components/button/overview) uses this technique with `<button mat-button>`. However, you wouldn't use this technique on a custom element. import {Component} from '@angular/core';/* avoid */@Component({ selector: '[tohHeroButton]', templateUrl: './hero-button.component.html',})export class HeroButtonComponent {} <!-- avoid --><div tohHeroButton></div> import {Component} from '@angular/core';@Component({ selector: 'toh-hero-button', templateUrl: './hero-button.component.html',})export class HeroButtonComponent {} <toh-hero-button></toh-hero-button> ### [Extract templates and styles to their own files](https://angular.dev/#extract-templates-and-styles-to-their-own-files) #### [Style 05-04](https://angular.dev/#style-05-04) **Do** extract templates and styles into a separate file, when more than 3 lines. **Do** name the template file `[component-name].component.html`, where \[component-name\] is the component name. **Do** name the style file `[component-name].component.css`, where \[component-name\] is the component name. **Do** specify _component-relative_ URLs, prefixed with `./`. **Why**? Large, inline templates and styles obscure the component's purpose and implementation, reducing readability and maintainability. **Why**? In most editors, syntax hints and code snippets aren't available when developing inline templates and styles. The Angular TypeScript Language Service (forthcoming) promises to overcome this deficiency for HTML templates in those editors that support it; it won't help with CSS styles. **Why**? A _component relative_ URL requires no change when you move the component files, as long as the files stay together. **Why**? The `./` prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix. import {Component, inject} from '@angular/core';import {Observable} from 'rxjs';import {Hero, HeroService} from './shared';import {AsyncPipe, NgFor, NgIf, UpperCasePipe} from '@angular/common';/* avoid */@Component({ selector: 'toh-heroes', template: ` <div> <h2>My Heroes</h2> <ul class="heroes"> @for (hero of heroes | async; track hero) { <li (click)="selectedHero=hero"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> } </ul> @if (selectedHero) { <div> <h2>{{selectedHero.name | uppercase}} is my hero</h2> </div> } </div> `, styles: [ ` .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `, ], imports: [NgFor, NgIf, AsyncPipe, UpperCasePipe],})export class HeroesComponent { selectedHero!: Hero; private heroService = inject(HeroService); heroes: Observable<Hero[]> = this.heroService.getHeroes();} import {Component, inject} from '@angular/core';import {Observable} from 'rxjs';import {Hero, HeroService} from './shared';import {AsyncPipe, NgFor, NgIf, UpperCasePipe} from '@angular/common';@Component({ selector: 'toh-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'], imports: [NgFor, NgIf, AsyncPipe, UpperCasePipe],})export class HeroesComponent { selectedHero!: Hero; private heroService = inject(HeroService); heroes: Observable<Hero[]> = this.heroService.getHeroes();} <div> <h2>My Heroes</h2> <ul class="heroes"> @for (hero of heroes | async; track hero) { <li> <button type="button" (click)="selectedHero=hero"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </button> </li> } </ul> @if (selectedHero) { <div> <h2>{{ selectedHero.name | uppercase }} is my hero</h2> </div> }</div> .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em;}.heroes li { display: flex;}.heroes button { flex: 1; cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: 0; border-radius: 4px; display: flex; align-items: stretch; height: 1.8em;}.heroes button:hover { color: #2c3a41; background-color: #e6e6e6; left: .1em;}.heroes button:active { background-color: #525252; color: #fafafa;}.heroes button.selected { background-color: black; color: white;}.heroes button.selected:hover { background-color: #505050; color: white;}.heroes button.selected:active { background-color: black; color: white;}.heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #405061; line-height: 1em; margin-right: .8em; border-radius: 4px 0 0 4px;}.heroes .name { align-self: center;} ### [Decorate `input` and `output` properties](https://angular.dev/#decorate-input-and-output-properties) #### [Style 05-12](https://angular.dev/#style-05-12) **Do** use the `@Input()` and `@Output()` class decorators instead of the `inputs` and `outputs` properties of the `@Directive` and `@Component` metadata: **Consider** placing `@Input()` or `@Output()` on the same line as the property it decorates. **Why**? It is easier and more readable to identify which properties in a class are inputs or outputs. **Why**? If you ever need to rename the property or event name associated with `@Input()` or `@Output()`, you can modify it in a single place. **Why**? The metadata declaration attached to the directive is shorter and thus more readable. **Why**? Placing the decorator on the same line _usually_ makes for shorter code and still easily identifies the property as an input or output. Put it on the line above when doing so is clearly more readable. import {Component, EventEmitter} from '@angular/core';/* avoid */@Component({ selector: 'toh-hero-button', template: `<button type="button"></button>`, inputs: ['label'], outputs: ['heroChange'],})export class HeroButtonComponent { heroChange = new EventEmitter<any>(); label: string = '';} import {Component, EventEmitter, Input, Output} from '@angular/core';@Component({ selector: 'toh-hero-button', template: `<button type="button">{{label}}</button>`,})export class HeroButtonComponent { @Output() heroChange = new EventEmitter<any>(); @Input() label = '';} ### [Avoid aliasing `inputs` and `outputs`](https://angular.dev/#avoid-aliasing-inputs-and-outputs) #### [Style 05-13](https://angular.dev/#style-05-13) **Avoid** `input` and `output` aliases except when it serves an important purpose. **Why**? Two names for the same property (one private, one public) is inherently confusing. **Why**? You should use an alias when the directive name is also an `input` property, and the directive name doesn't describe the property. import {Component, EventEmitter, Input, Output} from '@angular/core';/* avoid pointless aliasing */@Component({ selector: 'toh-hero-button', template: `<button type="button">{{label}}</button>`,})export class HeroButtonComponent { // Pointless aliases @Output('heroChangeEvent') heroChange = new EventEmitter<any>(); @Input('labelAttribute') label!: string;} <!-- avoid --><toh-hero-button labelAttribute="OK" (changeEvent)="doSomething()"></toh-hero-button> import {Component, EventEmitter, Input, Output} from '@angular/core';@Component({ selector: 'toh-hero-button', template: `<button type="button" >{{label}}</button>`,})export class HeroButtonComponent { // No aliases @Output() heroChange = new EventEmitter<any>(); @Input() label = '';} import {Directive, ElementRef, inject, Input, OnChanges} from '@angular/core';@Directive({ selector: '[heroHighlight]',})export class HeroHighlightDirective implements OnChanges { // Aliased because `color` is a better property name than `heroHighlight` @Input('heroHighlight') color = ''; private el = inject(ElementRef); ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.color || 'yellow'; }} <toh-hero-button label="OK" (change)="doSomething()"></toh-hero-button><!-- `heroHighlight` is both the directive name and the data-bound aliased property name --><h3 heroHighlight="skyblue">The Great Bombasto</h3> ### [Delegate complex component logic to services](https://angular.dev/#delegate-complex-component-logic-to-services) #### [Style 05-15](https://angular.dev/#style-05-15) **Do** limit logic in a component to only that required for the view. All other logic should be delegated to services. **Do** move reusable logic to services and keep components simple and focused on their intended purpose. **Why**? Logic may be reused by multiple components when placed within a service and exposed as a function. **Why**? Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked. **Why**? Removes dependencies and hides implementation details from the component. **Why**? Keeps the component slim, trim, and focused. /* avoid */import {Component, inject, OnInit} from '@angular/core';import {HttpClient} from '@angular/common/http';import {Observable} from 'rxjs';import {catchError, finalize} from 'rxjs/operators';import {Hero} from '../shared/hero.model';const heroesUrl = 'http://angular.io';@Component({ selector: 'toh-hero-list', template: `...`,})export class HeroListComponent { heroes: Hero[] = []; private http = inject(HttpClient); constructor() { this.getHeroes(); } getHeroes() { this.heroes = []; this.http .get(heroesUrl) .pipe( catchError(this.catchBadResponse), finalize(() => this.hideSpinner()), ) .subscribe((heroes: Hero[]) => (this.heroes = heroes)); } private catchBadResponse(err: any, source: Observable<any>) { // log and handle the exception return new Observable(); } private hideSpinner() { // hide the spinner }} import {Component, inject, OnInit} from '@angular/core';import {Hero, HeroService} from '../shared';@Component({ selector: 'toh-hero-list', template: `...`,})export class HeroListComponent implements OnInit { heroes: Hero[] = []; private heroService = inject(HeroService); getHeroes() { this.heroes = []; this.heroService.getHeroes().subscribe((heroes) => (this.heroes = heroes)); } ngOnInit() { this.getHeroes(); }} ### [Don't prefix `output` properties](https://angular.dev/#dont-prefix-output-properties) #### [Style 05-16](https://angular.dev/#style-05-16) **Do** name events without the prefix `on`. **Do** name event handler methods with the prefix `on` followed by the event name. **Why**? This is consistent with built-in events such as button clicks. **Why**? Angular allows for an [alternative syntax](https://angular.dev/guide/templates/binding) `on-*`. If the event itself was prefixed with `on` this would result in an `on-onEvent` binding expression. import {Component, EventEmitter, Output} from '@angular/core';/* avoid */@Component({ selector: 'toh-hero', template: `...`,})export class HeroComponent { @Output() onSavedTheDay = new EventEmitter<boolean>();} <!-- avoid --><toh-hero (onSavedTheDay)="onSavedTheDay($event)"></toh-hero> import {Component, EventEmitter, Output} from '@angular/core';@Component({ selector: 'toh-hero', template: `...`,})export class HeroComponent { @Output() savedTheDay = new EventEmitter<boolean>();} <toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero> ### [Put presentation logic in the component class](https://angular.dev/#put-presentation-logic-in-the-component-class) #### [Style 05-17](https://angular.dev/#style-05-17) **Do** put presentation logic in the component class, and not in the template. **Why**? Logic will be contained in one place (the component class) instead of being spread in two places. **Why**? Keeping the component's presentation logic in the class instead of the template improves testability, maintainability, and reusability. import {Component} from '@angular/core';import {Hero} from '../shared/hero.model';import {NgFor} from '@angular/common';import {HeroComponent} from '../hero/hero.component';/* avoid */@Component({ selector: 'toh-hero-list', template: ` <section> Our list of heroes: @for (hero of heroes; track hero) { <toh-hero [hero]="hero"></toh-hero> } Total powers: {{totalPowers}}<br> Average power: {{totalPowers / heroes.length}} </section> `, imports: [NgFor, HeroComponent],})export class HeroListComponent { heroes: Hero[] = []; totalPowers: number = 0;} import {Component} from '@angular/core';import {HeroComponent} from '../hero/hero.component';import {Hero} from '../shared/hero.model';import {NgFor} from '@angular/common';@Component({ selector: 'toh-hero-list', template: ` <section> Our list of heroes: @for (hero of heroes; track hero) { <toh-hero [hero]="hero"></toh-hero> } Total powers: {{totalPowers}}<br> Average power: {{avgPower}} </section> `, imports: [NgFor, HeroComponent],})export class HeroListComponent { heroes: Hero[]; totalPowers = 0; // testing harness constructor() { this.heroes = []; } get avgPower() { return this.totalPowers / this.heroes.length; }} ### [Initialize inputs](https://angular.dev/#initialize-inputs) #### [Style 05-18](https://angular.dev/#style-05-18) TypeScript's `--strictPropertyInitialization` compiler option ensures that a class initializes its properties during construction. When enabled, this option causes the TypeScript compiler to report an error if the class does not set a value to any property that is not explicitly marked as optional. By design, Angular treats all `@Input` properties as optional. When possible, you should satisfy `--strictPropertyInitialization` by providing a default value. import {Component, Input} from '@angular/core';@Component({ selector: 'toh-hero', template: `...`,})export class HeroComponent { @Input() id = 'default_id';} If the property is hard to construct a default value for, use `?` to explicitly mark the property as optional. import {Component, Input} from '@angular/core';@Component({ selector: 'toh-hero', template: `...`,})export class HeroComponent { @Input() id?: string; process() { if (this.id) { // ... } }} You may want to have a required `@Input` field, meaning all your component users are required to pass that attribute. In such cases, use a default value. Just suppressing the TypeScript error with `!` is insufficient and should be avoided because it will prevent the type checker from ensuring the input value is provided. import {Component, Input} from '@angular/core';@Component({ selector: 'toh-hero', template: `...`,})export class HeroComponent { // The exclamation mark suppresses errors that a property is // not initialized. // Ignoring this enforcement can prevent the type checker // from finding potential issues. @Input() id!: string;} ## [Directives](https://angular.dev/#directives) ### [Use directives to enhance an element](https://angular.dev/#use-directives-to-enhance-an-element) #### [Style 06-01](https://angular.dev/#style-06-01) **Do** use attribute directives when you have presentation logic without a template. **Why**? Attribute directives don't have an associated template. **Why**? An element may have more than one attribute directive applied. import {Directive, HostListener} from '@angular/core';@Directive({ selector: '[tohHighlight]',})export class HighlightDirective { @HostListener('mouseover') onMouseEnter() { // do highlight work }} <div tohHighlight>Bombasta</div> ### [`HostListener`/`HostBinding` decorators versus `host` metadata](https://angular.dev/#hostlistener-hostbinding-decorators-versus-host-metadata) #### [Style 06-03](https://angular.dev/#style-06-03) **Consider** preferring the `@HostListener` and `@HostBinding` to the `host` property of the `@Directive` and `@Component` decorators. **Do** be consistent in your choice. **Why**? The property associated with `@HostBinding` or the method associated with `@HostListener` can be modified only in a single place —in the directive's class. If you use the `host` metadata property, you must modify both the property/method declaration in the directive's class and the metadata in the decorator associated with the directive. import {Directive, HostBinding, HostListener} from '@angular/core';@Directive({ selector: '[tohValidator]',})export class ValidatorDirective { @HostBinding('attr.role') role = 'button'; @HostListener('mouseenter') onMouseEnter() { // do work }} Compare with the less preferred `host` metadata alternative. **Why**? The `host` metadata is only one term to remember and doesn't require extra ES imports. import {Directive} from '@angular/core';@Directive({ selector: '[tohValidator2]', host: { '[attr.role]': 'role', '(mouseenter)': 'onMouseEnter()', },})export class Validator2Directive { role = 'button'; onMouseEnter() { // do work }} ## [Services](https://angular.dev/#services) ### [Services are singletons](https://angular.dev/#services-are-singletons) #### [Style 07-01](https://angular.dev/#style-07-01) **Do** use services as singletons within the same injector. Use them for sharing data and functionality. **Why**? Services are ideal for sharing methods across a feature area or an app. **Why**? Services are ideal for sharing stateful in-memory data. import {inject, Injectable} from '@angular/core';import {HttpClient} from '@angular/common/http';import {Hero} from './hero.model';@Injectable()export class HeroService { private http = inject(HttpClient); getHeroes() { return this.http.get<Hero[]>('api/heroes'); }} ### [Providing a service](https://angular.dev/#providing-a-service) #### [Style 07-03](https://angular.dev/#style-07-03) **Do** provide a service with the application root injector in the `@Injectable` decorator of the service. **Why**? The Angular injector is hierarchical. **Why**? When you provide the service to a root injector, that instance of the service is shared and available in every class that needs the service. This is ideal when a service is sharing methods or state. **Why**? When you register a service in the `@Injectable` decorator of the service, optimization tools such as those used by the [Angular CLI's](https://angular.dev/cli) production builds can perform tree shaking and remove services that aren't used by your app. **Why**? This is not ideal when two different components need different instances of a service. In this scenario it would be better to provide the service at the component level that needs the new and separate instance. import {Injectable} from '@angular/core';@Injectable({ providedIn: 'root',})export class Service {} ### [Use the @Injectable() class decorator](https://angular.dev/#use-the-injectable-class-decorator) #### [Style 07-04](https://angular.dev/#style-07-04) **Do** use the `@Injectable()` class decorator instead of the `@Inject` parameter decorator when using types as tokens for the dependencies of a service. **Why**? The Angular Dependency Injection (DI) mechanism resolves a service's own dependencies based on the declared types of that service's constructor parameters. **Why**? When a service accepts only dependencies associated with type tokens, the `@Injectable()` syntax is much less verbose compared to using `@Inject()` on each individual constructor parameter. import {Inject} from '@angular/core';import {HttpClient} from '@angular/common/http';import {HeroService} from './hero.service';/* avoid */export class HeroArena { constructor( @Inject(HeroService) private heroService: HeroService, @Inject(HttpClient) private http: HttpClient, ) {}} import {Injectable} from '@angular/core';import {HttpClient} from '@angular/common/http';import {HeroService} from './hero.service';@Injectable()export class HeroArena { constructor( private heroService: HeroService, private http: HttpClient, ) {} // test harness getParticipants() { return this.heroService.getHeroes(); }} ## [Data Services](https://angular.dev/#data-services) ### [Talk to the server through a service](https://angular.dev/#talk-to-the-server-through-a-service) #### [Style 08-01](https://angular.dev/#style-08-01) **Do** refactor logic for making data operations and interacting with data to a service. **Do** make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations. **Why**? The component's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the component be simpler and more focused on the view. **Why**? This makes it easier to test (mock or real) the data calls when testing a component that uses a data service. **Why**? The details of data management, such as headers, HTTP methods, caching, error handling, and retry logic, are irrelevant to components and other data consumers. A data service encapsulates these details. It's easier to evolve these details inside the service without affecting its consumers. And it's easier to test the consumers with mock service implementations. ## [Lifecycle hooks](https://angular.dev/#lifecycle-hooks) Use Lifecycle hooks to tap into important events exposed by Angular. ### [Implement lifecycle hook interfaces](https://angular.dev/#implement-lifecycle-hook-interfaces) #### [Style 09-01](https://angular.dev/#style-09-01) **Do** implement the lifecycle hook interfaces. **Why**? Lifecycle interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes. import {Component} from '@angular/core';/* avoid */@Component({ selector: 'toh-hero-button', template: `<button type="button">OK</button>`,})export class HeroButtonComponent { onInit() { // misspelled console.log('The component is initialized'); }} import {Component, OnInit} from '@angular/core';@Component({ selector: 'toh-hero-button', template: `<button type="button">OK</button>`,})export class HeroButtonComponent implements OnInit { ngOnInit() { console.log('The component is initialized'); }} ## [Appendix](https://angular.dev/#appendix) Useful tools and tips for Angular. ### [File templates and snippets](https://angular.dev/#file-templates-and-snippets) #### [Style A-02](https://angular.dev/#style-a-02) **Do** use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs. **Consider** using [snippets](https://marketplace.visualstudio.com/items?itemName=johnpapa.Angular2) for [Visual Studio Code](https://code.visualstudio.com/) that follow these styles and guidelines. [](https://marketplace.visualstudio.com/items?itemName=johnpapa.Angular2) **Consider** using [snippets](https://github.com/orizens/sublime-angular2-snippets) for [Sublime Text](https://www.sublimetext.com/) that follow these styles and guidelines. **Consider** using [snippets](https://github.com/mhartington/vim-angular2-snippets) for [Vim](https://www.vim.org/) that follow these styles and guidelines. --- ## Page: https://angular.dev/best-practices/security This topic describes Angular's built-in protections against common web application vulnerabilities and attacks such as cross-site scripting attacks. It doesn't cover application-level security, such as authentication and authorization. For more information about the attacks and mitigations described below, see the [Open Web Application Security Project (OWASP) Guide](https://www.owasp.org/index.php/Category:OWASP_Guide_Project). ## [Best practices](https://angular.dev/#best-practices) These are some best practices to ensure that your Angular application is secure. 1. **Keep current with the latest Angular library releases** - The Angular libraries get regular updates, and these updates might fix security defects discovered in previous versions. Check the Angular [change log](https://github.com/angular/angular/blob/main/CHANGELOG.md) for security-related updates. 2. **Don't alter your copy of Angular** - Private, customized versions of Angular tend to fall behind the current version and might not include important security fixes and enhancements. Instead, share your Angular improvements with the community and make a pull request. 3. **Avoid Angular APIs marked in the documentation as "_Security Risk_"** - For more information, see the [Trusting safe values](https://angular.dev/#trusting-safe-values) section of this page. ## [Preventing cross-site scripting (XSS)](https://angular.dev/#preventing-cross-site-scripting-xss) [Cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) enables attackers to inject malicious code into web pages. Such code can then, for example, steal user and login data, or perform actions that impersonate the user. This is one of the most common attacks on the web. To block XSS attacks, you must prevent malicious code from entering the Document Object Model (DOM). For example, if attackers can trick you into inserting a `<script>` tag in the DOM, they can run arbitrary code on your website. The attack isn't limited to `<script>` tags —many elements and properties in the DOM allow code execution, for example, `<img alt="" onerror="...">` and `<a href="javascript:...">`. If attacker-controlled data enters the DOM, expect security vulnerabilities. ### [Angular's cross-site scripting security model](https://angular.dev/#angulars-cross-site-scripting-security-model) To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value is inserted into the DOM from a template binding, or interpolation, Angular sanitizes and escapes untrusted values. If a value was already sanitized outside of Angular and is considered safe, communicate this to Angular by marking the [value as trusted](https://angular.dev/#trusting-safe-values). Unlike values to be used for rendering, Angular templates are considered trusted by default, and should be treated as executable code. Never create templates by concatenating user input and template syntax. Doing this would enable attackers to [inject arbitrary code](https://en.wikipedia.org/wiki/Code_injection) into your application. To prevent these vulnerabilities, always use the default [Ahead-Of-Time (AOT) template compiler](https://angular.dev/#use-the-aot-template-compiler) in production deployments. An extra layer of protection can be provided through the use of Content security policy and Trusted Types. These web platform features operate at the DOM level which is the most effective place to prevent XSS issues. Here they can't be bypassed using other, lower-level APIs. For this reason, it is strongly encouraged to take advantage of these features. To do this, configure the [content security policy](https://angular.dev/#content-security-policy) for the application and enable [trusted types enforcement](https://angular.dev/#enforcing-trusted-types). ### [Sanitization and security contexts](https://angular.dev/#sanitization-and-security-contexts) _Sanitization_ is the inspection of an untrusted value, turning it into a value that's safe to insert into the DOM. In many cases, sanitization doesn't change a value at all. Sanitization depends on a context. For example, a value that's harmless in CSS is potentially dangerous in a URL. Angular defines the following security contexts: | Security contexts | Details | | --- | --- | | HTML | Used when interpreting a value as HTML, for example, when binding to `innerHtml`. | | Style | Used when binding CSS into the `style` property. | | URL | Used for URL properties, such as `<a href>`. | | Resource URL | A URL that is loaded and executed as code, for example, in `<script src>`. | Angular sanitizes untrusted values for HTML and URLs. Sanitizing resource URLs isn't possible because they contain arbitrary code. In development mode, Angular prints a console warning when it has to change a value during sanitization. ### [Sanitization example](https://angular.dev/#sanitization-example) The following template binds the value of `htmlSnippet`. Once by interpolating it into an element's content, and once by binding it to the `innerHTML` property of an element: <h3>Binding innerHTML</h3><p>Bound value:</p><p class="e2e-inner-html-interpolated">{{ htmlSnippet }}</p><p>Result of binding to innerHTML:</p><p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p> Interpolated content is always escaped —the HTML isn't interpreted and the browser displays angle brackets in the element's text content. For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`. Be aware that binding a value that an attacker might control into `innerHTML` normally causes an XSS vulnerability. For example, one could run JavaScript in a following way: import {Component} from '@angular/core';@Component({ selector: 'app-inner-html-binding', templateUrl: './inner-html-binding.component.html',})export class InnerHtmlBindingComponent { // For example, a user/attacker-controlled value from a URL. htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';} Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `script` element but keeps safe content such as the `<b>` element.  ### [Direct use of the DOM APIs and explicit sanitization calls](https://angular.dev/#direct-use-of-the-dom-apis-and-explicit-sanitization-calls) Unless you enforce Trusted Types, the built-in browser DOM APIs don't automatically protect you from security vulnerabilities. For example, `document`, the node available through `ElementRef`, and many third-party APIs contain unsafe methods. Likewise, if you interact with other libraries that manipulate the DOM, you likely won't have the same automatic sanitization as with Angular interpolations. Avoid directly interacting with the DOM and instead use Angular templates where possible. For cases where this is unavoidable, use the built-in Angular sanitization functions. Sanitize untrusted values with the [DomSanitizer.sanitize](https://angular.dev/api/platform-browser/DomSanitizer#sanitize) method and the appropriate `SecurityContext`. That function also accepts values that were marked as trusted using the `bypassSecurityTrust` functions, and does not sanitize them, as [described below](https://angular.dev/#trusting-safe-values). ### [Trusting safe values](https://angular.dev/#trusting-safe-values) Sometimes applications genuinely need to include executable code, display an `<iframe>` from some URL, or construct potentially dangerous URLs. To prevent automatic sanitization in these situations, tell Angular that you inspected a value, checked how it was created, and made sure it is secure. Do _be careful_. If you trust a value that might be malicious, you are introducing a security vulnerability into your application. If in doubt, find a professional security reviewer. To mark a value as trusted, inject `DomSanitizer` and call one of the following methods: * `bypassSecurityTrustHtml` * `bypassSecurityTrustScript` * `bypassSecurityTrustStyle` * `bypassSecurityTrustUrl` * `bypassSecurityTrustResourceUrl` Remember, whether a value is safe depends on context, so choose the right context for your intended use of the value. Imagine that the following template needs to bind a URL to a `javascript:alert(...)` call: <h3>Bypass Security Component</h3><h4>An untrusted URL:</h4><p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me</a></p><h4>A trusted URL:</h4><p><a class="e2e-trusted-url" [href]="trustedUrl">Click me</a></p><h4>Resource URL:</h4><p>Showing: {{ dangerousVideoUrl }}</p><p>Trusted:</p><iframe class="e2e-iframe-trusted-src" width="640" height="390" [src]="videoUrl" title="trusted video url"></iframe><p>Untrusted:</p><iframe class="e2e-iframe-untrusted-src" width="640" height="390" [src]="dangerousVideoUrl" title="unTrusted video url"></iframe> Normally, Angular automatically sanitizes the URL, disables the dangerous code, and in development mode, logs this action to the console. To prevent this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call: import {Component, inject} from '@angular/core';import {DomSanitizer, SafeResourceUrl, SafeUrl} from '@angular/platform-browser';@Component({ selector: 'app-bypass-security', templateUrl: './bypass-security.component.html',})export class BypassSecurityComponent { dangerousUrl: string; trustedUrl: SafeUrl; dangerousVideoUrl!: string; videoUrl!: SafeResourceUrl; private sanitizer = inject(DomSanitizer); constructor() { // javascript: URLs are dangerous if attacker controlled. // Angular sanitizes them in data binding, but you can // explicitly tell Angular to trust this value: this.dangerousUrl = 'javascript:alert("Hi there")'; this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(this.dangerousUrl); this.updateVideoUrl('PUBnlbjZFAI'); } updateVideoUrl(id: string) { // Appending an ID to a YouTube URL is safe. // Always make sure to construct SafeValue objects as // close as possible to the input data so // that it's easier to check if the value is safe. this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id; this.videoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl); }}  If you need to convert user input into a trusted value, use a component method. The following template lets users enter a YouTube video ID and load the corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users could run. To prevent this, call a method on the component to construct a trusted video URL, which causes Angular to let binding into `<iframe src>`: <h3>Bypass Security Component</h3><h4>An untrusted URL:</h4><p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me</a></p><h4>A trusted URL:</h4><p><a class="e2e-trusted-url" [href]="trustedUrl">Click me</a></p><h4>Resource URL:</h4><p>Showing: {{ dangerousVideoUrl }}</p><p>Trusted:</p><iframe class="e2e-iframe-trusted-src" width="640" height="390" [src]="videoUrl" title="trusted video url"></iframe><p>Untrusted:</p><iframe class="e2e-iframe-untrusted-src" width="640" height="390" [src]="dangerousVideoUrl" title="unTrusted video url"></iframe> import {Component, inject} from '@angular/core';import {DomSanitizer, SafeResourceUrl, SafeUrl} from '@angular/platform-browser';@Component({ selector: 'app-bypass-security', templateUrl: './bypass-security.component.html',})export class BypassSecurityComponent { dangerousUrl: string; trustedUrl: SafeUrl; dangerousVideoUrl!: string; videoUrl!: SafeResourceUrl; private sanitizer = inject(DomSanitizer); constructor() { // javascript: URLs are dangerous if attacker controlled. // Angular sanitizes them in data binding, but you can // explicitly tell Angular to trust this value: this.dangerousUrl = 'javascript:alert("Hi there")'; this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(this.dangerousUrl); this.updateVideoUrl('PUBnlbjZFAI'); } updateVideoUrl(id: string) { // Appending an ID to a YouTube URL is safe. // Always make sure to construct SafeValue objects as // close as possible to the input data so // that it's easier to check if the value is safe. this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id; this.videoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl); }} ### [Content security policy](https://angular.dev/#content-security-policy) Content Security Policy (CSP) is a defense-in-depth technique to prevent XSS. To enable CSP, configure your web server to return an appropriate `Content-Security-Policy` HTTP header. Read more about content security policy at the [Web Fundamentals guide](https://developers.google.com/web/fundamentals/security/csp) on the Google Developers website. The minimal policy required for a brand-new Angular application is: default-src 'self'; style-src 'self' 'nonce-randomNonceGoesHere'; script-src 'self' 'nonce-randomNonceGoesHere'; When serving your Angular application, the server should include a randomly-generated nonce in the HTTP header for each request. You must provide this nonce to Angular so that the framework can render `<style>` elements. You can set the nonce for Angular in one of two ways: 1. Set the `ngCspNonce` attribute on the root application element as `<app ngCspNonce="randomNonceGoesHere"></app>`. Use this approach if you have access to server-side templating that can add the nonce both to the header and the `index.html` when constructing the response. 2. Provide the nonce using the `CSP_NONCE` injection token. Use this approach if you have access to the nonce at runtime and you want to be able to cache the `index.html`. import {bootstrapApplication, CSP_NONCE} from '@angular/core';import {AppComponent} from './app/app.component';bootstrapApplication(AppComponent, { providers: [{ provide: CSP_NONCE, useValue: globalThis.myRandomNonceValue }]}); ### Unique nonces Always ensure that the nonces you provide are **unique per request** and that they are not predictable or guessable. If an attacker can predict future nonces, they can circumvent the protections offered by CSP. If you cannot generate nonces in your project, you can allow inline styles by adding `'unsafe-inline'` to the `style-src` section of the CSP header. | Sections | Details | | --- | --- | | `default-src 'self';` | Allows the page to load all its required resources from the same origin. | | `style-src 'self' 'nonce-randomNonceGoesHere';` | Allows the page to load global styles from the same origin (`'self'`) and styles inserted by Angular with the `nonce-randomNonceGoesHere`. | | `script-src 'self' 'nonce-randomNonceGoesHere';` | Allows the page to load JavaScript from the same origin (`'self'`) and scripts inserted by the Angular CLI with the `nonce-randomNonceGoesHere`. This is only required if you're using critical CSS inlining. | Angular itself requires only these settings to function correctly. As your project grows, you may need to expand your CSP settings to accommodate extra features specific to your application. ### [Enforcing Trusted Types](https://angular.dev/#enforcing-trusted-types) It is recommended that you use [Trusted Types](https://w3c.github.io/trusted-types/dist/spec/) as a way to help secure your applications from cross-site scripting attacks. Trusted Types is a [web platform](https://en.wikipedia.org/wiki/Web_platform) feature that can help you prevent cross-site scripting attacks by enforcing safer coding practices. Trusted Types can also help simplify the auditing of application code. ### Trusted types Trusted Types might not yet be available in all browsers your application targets. In the case your Trusted-Types-enabled application runs in a browser that doesn't support Trusted Types, the features of the application are preserved. Your application is guarded against XSS by way of Angular's DomSanitizer. See [caniuse.com/trusted-types](https://caniuse.com/trusted-types) for the current browser support. To enforce Trusted Types for your application, you must configure your application's web server to emit HTTP headers with one of the following Angular policies: | Policies | Detail | | --- | --- | | `angular` | This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy. | | `angular#bundler` | This policy is used by the Angular CLI bundler when creating lazy chunk files. | | `angular#unsafe-bypass` | This policy is used for applications that use any of the methods in Angular's [DomSanitizer](https://angular.dev/api/platform-browser/DomSanitizer) that bypass security, such as `bypassSecurityTrustHtml`. Any application that uses these methods must enable this policy. | | `angular#unsafe-jit` | This policy is used by the [Just-In-Time (JIT) compiler](https://angular.dev/api/core/Compiler). You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the [platform browser dynamic](https://angular.dev/api/platform-browser-dynamic/platformBrowserDynamic). | | `angular#unsafe-upgrade` | This policy is used by the [@angular/upgrade](https://angular.dev/api/upgrade/static/UpgradeModule) package. You must enable this policy if your application is an AngularJS hybrid. | You should configure the HTTP headers for Trusted Types in the following locations: * Production serving infrastructure * Angular CLI (`ng serve`), using the `headers` property in the `angular.json` file, for local development and end-to-end testing * Karma (`ng test`), using the `customHeaders` property in the `karma.config.js` file, for unit testing The following is an example of a header specifically configured for Trusted Types and Angular: Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script'; An example of a header specifically configured for Trusted Types and Angular applications that use any of Angular's methods in [DomSanitizer](https://angular.dev/api/platform-browser/DomSanitizer) that bypasses security: Content-Security-Policy: trusted-types angular angular#unsafe-bypass; require-trusted-types-for 'script'; The following is an example of a header specifically configured for Trusted Types and Angular applications using JIT: Content-Security-Policy: trusted-types angular angular#unsafe-jit; require-trusted-types-for 'script'; The following is an example of a header specifically configured for Trusted Types and Angular applications that use lazy loading of modules: Content-Security-Policy: trusted-types angular angular#bundler; require-trusted-types-for 'script'; ### [Use the AOT template compiler](https://angular.dev/#use-the-aot-template-compiler) The AOT template compiler prevents a whole class of vulnerabilities called template injection, and greatly improves application performance. The AOT template compiler is the default compiler used by Angular CLI applications, and you should use it in all production deployments. An alternative to the AOT compiler is the JIT compiler which compiles templates to executable template code within the browser at runtime. Angular trusts template code, so dynamically generating templates and compiling them, in particular templates containing user data, circumvents Angular's built-in protections. This is a security anti-pattern. For information about dynamically constructing forms in a safe way, see the [Dynamic Forms](https://angular.dev/guide/forms/dynamic-forms) guide. ### [Server-side XSS protection](https://angular.dev/#server-side-xss-protection) HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an Angular application is the same as injecting executable code into the application: It gives the attacker full control over the application. To prevent this, use a templating language that automatically escapes values to prevent XSS vulnerabilities on the server. Don't create Angular templates on the server side using a templating language. This carries a high risk of introducing template-injection vulnerabilities. ## [HTTP-level vulnerabilities](https://angular.dev/#http-level-vulnerabilities) Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily on the server side, but Angular provides helpers to make integration on the client side easier. ### [Cross-site request forgery](https://angular.dev/#cross-site-request-forgery) In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting a different web page (such as `evil.com`) with malignant code. This web page secretly sends a malicious request to the application's web server (such as `example-bank.com`). Assume the user is logged into the application at `example-bank.com`. The user opens an email and clicks a link to `evil.com`, which opens in a new tab. The `evil.com` page immediately sends a malicious request to `example-bank.com`. Perhaps it's a request to transfer money from the user's account to the attacker's account. The browser automatically sends the `example-bank.com` cookies, including the authentication cookie, with this request. If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate request from the application and the forged request from `evil.com`. To prevent this, the application must ensure that a user request originates from the real application, not from a different site. The server and client must cooperate to thwart this attack. In a common anti-XSRF technique, the application server sends a randomly created authentication token in a cookie. The client code reads the cookie and adds a custom request header with the token in all following requests. The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match. This technique is effective because all browsers implement the _same origin policy_. Only code from the website on which cookies are set can read the cookies from that site and set custom headers on requests to that site. That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't. ### [`HttpClient` XSRF/CSRF security](https://angular.dev/#httpclient-xsrf-csrf-security) `HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Because only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker. By default, an interceptor sends this header on all mutating requests (such as `POST`) to relative URLs, but not on GET/HEAD requests or on requests with an absolute URL. ### Why not protect GET requests? CSRF protection is only needed for requests that can change state on the backend. By their nature, CSRF attacks cross domain boundaries, and the web's [same-origin policy](https://developer.mozilla.org/docs/Web/Security/Same-origin_policy) will prevent an attacking page from retrieving the results of authenticated GET requests. To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication cookie with a salt for added security. To prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name. ### HttpClient supports only the client half of the XSRF protection scheme Your backend service must be configured to set the cookie for your page, and to verify that the header is present on all eligible requests. Failing to do so renders Angular's default protection ineffective. 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](https://angular.dev/#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(), ), ]}; For information about CSRF at the Open Web Application Security Project (OWASP), see [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) and [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html). The Stanford University paper [Robust Defenses for Cross-Site Request Forgery](https://seclab.stanford.edu/websec/csrf/csrf.pdf) is a rich source of detail. See also Dave Smith's [talk on XSRF at AngularConnect 2016](https://www.youtube.com/watch?v=9inczw6qtpY "Cross"). ### [Cross-site script inclusion (XSSI)](https://angular.dev/#cross-site-script-inclusion-xssi) Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to read data from a JSON API. The attack works on older browsers by overriding built-in JavaScript object constructors, and then including an API URL using a `<script>` tag. This attack is only successful if the returned JSON is executable as JavaScript. Servers can prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the well-known string `")]}',\n"`. Angular's `HttpClient` library recognizes this convention and automatically strips the string `")]}',\n"` from all responses before further parsing. For more information, see the XSSI section of this [Google web security blog post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html). ## [Auditing Angular applications](https://angular.dev/#auditing-angular-applications) Angular applications must follow the same security principles as regular web applications, and must be audited as such. Angular-specific APIs that should be audited in a security review, such as the [_bypassSecurityTrust_](https://angular.dev/#trusting-safe-values) methods, are marked in the documentation as security sensitive. --- ## Page: https://angular.dev/best-practices/a11y The web is used by a wide variety of people, including those who have visual or motor impairments. A variety of assistive technologies are available that make it much easier for these groups to interact with web-based software applications. Also, designing an application to be more accessible generally improves the user experience for all users. For an in-depth introduction to issues and techniques for designing accessible applications, see Google's web.dev [Learn Accessibility](https://web.dev/learn/accessibility/) course. This page discusses best practices for designing Angular applications that work well for all users, including those who rely on assistive technologies. ## [Accessibility attributes](https://angular.dev/#accessibility-attributes) Building accessible web experience often involves setting [Accessible Rich Internet Applications (ARIA) attributes](https://web.dev/learn/accessibility/aria-html/) to provide semantic meaning where it might otherwise be missing. Use attribute binding template syntax to control the values of accessibility-related attributes. When binding to ARIA attributes in Angular, you must use the `attr.` prefix. The ARIA specification depends specifically on HTML attributes rather than properties of DOM elements. <!-- Use attr. when binding to an ARIA attribute --><button [attr.aria-label]="myActionLabel">…</button> **NOTE:** This syntax is only necessary for attribute _bindings_. Static ARIA attributes require no extra syntax. <!-- Static ARIA attributes require no extra syntax --><button aria-label="Save document">…</button> **HELPFUL:** By convention, HTML attributes use lowercase names (`tabindex`), while properties use camelCase names (`tabIndex`). See the [Binding syntax guide](https://angular.dev/guide/templates) for more background on the difference between attributes and properties. ## [Angular UI components](https://angular.dev/#angular-ui-components) The [Angular Material](https://material.angular.io/) library, which is maintained by the Angular team, is a suite of reusable UI components that aims to be fully accessible. The [Component Development Kit (CDK)](https://material.angular.io/cdk/categories) includes the `a11y` package that provides tools to support various areas of accessibility. For example: * `LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region. See the W3C documentation for more information on [aria-live regions](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties#aria-live). * The `cdkTrapFocus` directive traps Tab-key focus within an element. Use it to create accessible experience for components such as modal dialogs, where focus must be constrained. For full details of these and other tools, see the [Angular CDK accessibility overview](https://material.angular.io/cdk/a11y/overview). ### [Augmenting native elements](https://angular.dev/#augmenting-native-elements) Native HTML elements capture several standard interaction patterns that are important to accessibility. When authoring Angular components, you should re-use these native elements directly when possible, rather than re-implementing well-supported behaviors. For example, instead of creating a custom element for a new variety of button, create a component that uses an attribute selector with a native `<button>` element. This most commonly applies to `<button>` and `<a>`, but can be used with many other types of element. You can see examples of this pattern in Angular Material: [`MatButton`](https://github.com/angular/components/blob/main/src/material/button/button.ts#L33C3-L36C5), [`MatTabNav`](https://github.com/angular/components/blob/main/src/material/tabs/tab-nav-bar/tab-nav-bar.ts#L62), and [`MatTable`](https://github.com/angular/components/blob/main/src/material/table/table.ts#L40). ### [Using containers for native elements](https://angular.dev/#using-containers-for-native-elements) Sometimes using the appropriate native element requires a container element. For example, the native `<input>` element cannot have children, so any custom text entry components need to wrap an `<input>` with extra elements. By just including `<input>` in your custom component's template, it's impossible for your component's users to set arbitrary properties and attributes to the `<input>` element. Instead, create a container component that uses content projection to include the native control in the component's API. You can see [`MatFormField`](https://material.angular.io/components/form-field/overview) as an example of this pattern. ## [Case study: Building a custom progress bar](https://angular.dev/#case-study-building-a-custom-progress-bar) The following example shows how to make a progress bar accessible by using host binding to control accessibility-related attributes. * The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes. The ARIA attribute `aria-valuenow` is bound to the user's input. * In the template, the `aria-label` attribute ensures that the control is accessible to screen readers. 1import {Component, Input} from '@angular/core';23/**4 * Example progressbar component.5 */6@Component({7 selector: 'app-example-progressbar',8 template: '<div class="bar" [style.width.%]="value"></div>',9 styleUrls: ['./progress-bar.component.css'],10 host: {11 // Sets the role for this component to "progressbar"12 role: 'progressbar',1314 // Sets the minimum and maximum values for the progressbar role.15 'aria-valuemin': '0',16 'aria-valuemax': '100',1718 // Binding that updates the current value of the progressbar.19 '[attr.aria-valuenow]': 'value',20 },21})22export class ExampleProgressbarComponent {23 /** Current value of the progressbar. */24 @Input() value = 0;25}2627 1<h1>Accessibility Example</h1>2<label for="progress-value">3 Enter an example progress value4 <input id="progress-value" type="number" min="0" max="100"5 [value]="progress" (input)="setProgress($event)">6</label>78<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->9<app-example-progressbar [value]="progress" aria-label="Example of a progress bar">10</app-example-progressbar>1112 ## [Routing](https://angular.dev/#routing) ### [Focus management after navigation](https://angular.dev/#focus-management-after-navigation) Tracking and controlling [focus](https://web.dev/learn/accessibility/focus/) in a UI is an important consideration in designing for accessibility. When using Angular routing, you should decide where page focus goes upon navigation. To avoid relying solely on visual cues, you need to make sure your routing code updates focus after page navigation. Use the `NavigationEnd` event from the `Router` service to know when to update focus. The following example shows how to find and focus the main content header in the DOM after navigation. router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => { const mainHeader = document.querySelector('#main-content-header') if (mainHeader) { mainHeader.focus(); }}); In a real application, the element that receives focus depends on your specific application structure and layout. The focused element should put users in a position to immediately move into the main content that has just been routed into view. You should avoid situations where focus returns to the `body` element after a route change. ### [Active links identification](https://angular.dev/#active-links-identification) CSS classes applied to active `RouterLink` elements, such as `RouterLinkActive`, provide a visual cue to identify the active link. Unfortunately, a visual cue doesn't help blind or visually impaired users. Applying the `aria-current` attribute to the element can help identify the active link. For more information, see [Mozilla Developer Network (MDN) aria-current](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Attributes/aria-current)). The `RouterLinkActive` directive provides the `ariaCurrentWhenActive` input which sets the `aria-current` to a specified value when the link becomes active. The following example shows how to apply the `active-page` class to active links as well as setting their `aria-current` attribute to `"page"` when they are active: <nav> <a routerLink="home" routerLinkActive="active-page" ariaCurrentWhenActive="page"> Home </a> <a routerLink="about" routerLinkActive="active-page" ariaCurrentWhenActive="page"> About </a> <a routerLink="shop" routerLinkActive="active-page" ariaCurrentWhenActive="page"> Shop </a></nav> ## [More information](https://angular.dev/#more-information) * [Accessibility - Google Web Fundamentals](https://developers.google.com/web/fundamentals/accessibility) * [ARIA specification and authoring practices](https://www.w3.org/TR/wai-aria) * [Material Design - Accessibility](https://material.io/design/usability/accessibility.html) * [Smashing Magazine](https://www.smashingmagazine.com/search/?q=accessibility) * [Inclusive Components](https://inclusive-components.design/) * [Accessibility Resources and Code Examples](https://dequeuniversity.com/resources) * [W3C - Web Accessibility Initiative](https://www.w3.org/WAI/people-use-web) * [Rob Dodson A11ycasts](https://www.youtube.com/watch?v=HtTyRajRuyY) * [Angular ESLint](https://github.com/angular-eslint/angular-eslint#functionality) provides linting rules that can help you make sure your code meets accessibility standards. Books * "A Web for Everyone: Designing Accessible User Experiences," Sarah Horton and Whitney Quesenbery * "Inclusive Design Patterns," Heydon Pickering --- ## Page: https://angular.dev/best-practices/runtime-performance Fast rendering is critical for Angular and we've built the framework with a lot of optimizations in mind to help you develop performant apps. To better understand the performance of your app we offer [Angular DevTools](https://angular.dev/tools/devtools) and a [video guide](https://www.youtube.com/watch?v=FjyX_hkscII) on how to use Chrome DevTools for profiling. In this section we cover the most common performance optimization techniques. **Change detection** is the process through which Angular checks to see whether your application state has changed, and if any DOM needs to be updated. At a high level, Angular walks your components from top to bottom, looking for changes. Angular runs its change detection mechanism periodically so that changes to the data model are reflected in an application’s view. Change detection can be triggered either manually or through an asynchronous event (for example, a user interaction or an XMLHttpRequest completion). Change detection is highly optimized and performant, but it can still cause slowdowns if the application runs it too frequently. In this guide, you’ll learn how to control and optimize the change detection mechanism by skipping parts of your application and running change detection only when necessary. Watch this video if you prefer to learn more about performance optimizations in a media format: --- ## Page: https://angular.dev/guide/ngmodules/overview **IMPORTANT:** The Angular team recommends using [standalone components](https://angular.dev/guide/components/anatomy-of-components#-imports-in-the-component-decorator) instead of `NgModule` for all new code. Use this guide to understand existing code built with `@NgModule`. An NgModule is a class marked by the `@NgModule` decorator. This decorator accepts _metadata_ that tells Angular how to compile component templates and configure dependency injection. import {NgModule} from '@angular/core';@NgModule({ // Metadata goes here})export class CustomMenuModule { } An NgModule has two main responsibilities: * Declaring components, directives, and pipes that belong to the NgModule * Add providers to the injector for components, directives, and pipes that import the NgModule ## [Declarations](https://angular.dev/#declarations) The `declarations` property of the `@NgModule` metadata declares the components, directives, and pipes that belong to the NgModule. @NgModule({ /* ... */ // CustomMenu and CustomMenuItem are components. declarations: [CustomMenu, CustomMenuItem],})export class CustomMenuModule { } In the example above, the components `CustomMenu` and `CustomMenuItem` belong to `CustomMenuModule`. The `declarations` property additionally accepts _arrays_ of components, directives, and pipes. These arrays, in turn, may also contain other arrays. const MENU_COMPONENTS = [CustomMenu, CustomMenuItem];const WIDGETS = [MENU_COMPONENTS, CustomSlider];@NgModule({ /* ... */ // This NgModule declares all of CustomMenu, CustomMenuItem, // CustomSlider, and CustomCheckbox. declarations: [WIDGETS, CustomCheckbox],})export class CustomMenuModule { } If Angular discovers any components, directives, or pipes declared in more than one NgModule, it reports an error. Any components, directives, or pipes must be explicitly marked as `standalone: false` in order to be declared in an NgModule. @Component({ // Mark this component as `standalone: false` so that it can be declared in an NgModule. standalone: false, /* ... */})export class CustomMenu { /* ... */ } ### [imports](https://angular.dev/#imports) Components declared in an NgModule may depend on other components, directives, and pipes. Add these dependencies to the `imports` property of the `@NgModule` metadata. @NgModule({ /* ... */ // CustomMenu and CustomMenuItem depend on the PopupTrigger and SelectorIndicator components. imports: [PopupTrigger, SelectionIndicator], declarations: [CustomMenu, CustomMenuItem],})export class CustomMenuModule { } The `imports` array accepts other NgModules, as well as standalone components, directives, and pipes. ### [exports](https://angular.dev/#exports) An NgModule can _export_ its declared components, directives, and pipes such that they're available to other components and NgModules. @NgModule({ imports: [PopupTrigger, SelectionIndicator], declarations: [CustomMenu, CustomMenuItem], // Make CustomMenu and CustomMenuItem available to // components and NgModules that import CustomMenuModule. exports: [CustomMenu, CustomMenuItem],})export class CustomMenuModule { } The `exports` property is not limited to declarations, however. An NgModule can also export any other components, directives, pipes, and NgModules that it imports. @NgModule({ imports: [PopupTrigger, SelectionIndicator], declarations: [CustomMenu, CustomMenuItem], // Also make PopupTrigger available to any component or NgModule that imports CustomMenuModule. exports: [CustomMenu, CustomMenuItem, PopupTrigger],})export class CustomMenuModule { } ## [`NgModule` providers](https://angular.dev/#ngmodule-providers) An `NgModule` can specify `providers` for injected dependencies. These providers are available to: * Any standalone component, directive, or pipe that imports the NgModule, and * The `declarations` and `providers` of any _other_ NgModule that imports the NgModule. @NgModule({ imports: [PopupTrigger, SelectionIndicator], declarations: [CustomMenu, CustomMenuItem], // Provide the OverlayManager service providers: [OverlayManager], /* ... */})export class CustomMenuModule { }@NgModule({ imports: [CustomMenuModule], declarations: [UserProfile], providers: [UserDataClient],})export class UserProfileModule { } In the example above: * The `CustomMenuModule` provides `OverlayManager`. * The `CustomMenu` and `CustomMenuItem` components can inject `OverlayManager` because they're declared in `CustomMenuModule`. * `UserProfile` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`. * `UserDataClient` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`. ### [The `forRoot` and `forChild` pattern](https://angular.dev/#the-forroot-and-forchild-pattern) Some NgModules define a static `forRoot` method that accepts some configuration and returns an array of providers. The name "`forRoot`" is a convention that indicates that these providers are intended to be added exclusively to the _root_ of your application during bootstrap. Any providers included in this way are eagerly loaded, increasing the JavaScript bundle size of your initial page load. boorstrapApplication(MyApplicationRoot, { providers: [ CustomMenuModule.forRoot(/* some config */), ],}); Similarly, some NgModules may define a static `forChild` that indicates the providers are intended to be added to components within your application hierarchy. @Component({ /* ... */ providers: [ CustomMenuModule.forChild(/* some config */), ],})export class UserProfile { /* ... */ } ## [Bootstrapping an application](https://angular.dev/#bootstrapping-an-application) **IMPORTANT:** The Angular team recommends using [bootstrapApplication](https://angular.dev/api/platform-browser/bootstrapApplication) instead of `bootstrapModule` for all new code. Use this guide to understand existing applications bootstrapped with `@NgModule`. The `@NgModule` decorator accepts an optional `bootstrap` array that may contain one or more components. You can use the [`bootstrapModule`](https://angular.dev/api/core/PlatformRef#bootstrapModule) method from either [`platformBrowser`](https://angular.dev/api/platform-browser/platformBrowser) or [`platformServer`](https://angular.dev/api/platform-server/platformServer) to start an Angular application. When run, this function locates any elements on the page with a CSS selector that matches the listed componet(s) and renders those components on the page. import {platformBrowser} from '@angular/platform-browser';@NgModule({ bootstrap: [MyApplication],})export class MyApplicationModule { }platformBrowser().bootstrapModule(MyApplicationModule); Components listed in `bootstrap` are automatically included in the NgModule's declarations. When you bootstrap an application from an NgModule, the collected `providers` of this module and all of the `providers` of its `imports` are eagerly loaded and available to inject for the entire application. --- ## Page: https://angular.dev/guide/animations **IMPORTANT:** The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](https://angular.dev/guide/animations/migration) to learn how you can start using pure CSS animations in your apps. Animation provides the illusion of motion: HTML elements change styling over time. 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 intuitively call the user's attention to where it is needed Typically, animations involve multiple style _transformations_ over time. An HTML element can move, change color, grow or shrink, fade, or slide off the page. These changes can occur simultaneously or sequentially. You can control the timing of each transformation. Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1) page. ## [About this guide](https://angular.dev/#about-this-guide) This guide covers the basic Angular animation features to get you started on adding Angular animations to your project. ## [Getting started](https://angular.dev/#getting-started) The main Angular modules for animations are `@angular/animations` and `@angular/platform-browser`. To get started with adding Angular animations to your project, import the animation-specific modules along with standard Angular functionality. 1. ### [Enabling the animations module](https://angular.dev/#enabling-the-animations-module) Import `provideAnimationsAsync` from `@angular/platform-browser/animations/async` and add it to the providers list in the `bootstrapApplication` function call. 1bootstrapApplication(AppComponent, {2 providers: [3 provideAnimationsAsync(),4 ]5}); ### If you need immediate animations in your application If you need to have an animation happen immediately when your application is loaded, you will want to switch to the eagerly loaded animations module. Import `provideAnimations` from `@angular/platform-browser/animations` instead, and use `provideAnimations` **in place of** `provideAnimationsAsync` in the `bootstrapApplication` function call. For `NgModule` based applications import `BrowserAnimationsModule`, which introduces the animation capabilities into your Angular root application module. import {NgModule} from '@angular/core';import {BrowserModule} from '@angular/platform-browser';import {BrowserAnimationsModule} from '@angular/platform-browser/animations';@NgModule({ imports: [BrowserModule, BrowserAnimationsModule], declarations: [], bootstrap: [],})export class AppModule {} 2. ### [Importing animation functions into component files](https://angular.dev/#importing-animation-functions-into-component-files) If you plan to use specific animation functions in component files, import those functions from `@angular/animations`. import {Component, HostBinding, inject} from '@angular/core';import { trigger, state, style, animate, transition, // ...} from '@angular/animations';import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';import {slideInAnimation} from './animations';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'], imports: [RouterLink, RouterOutlet], animations: [ slideInAnimation, // animation triggers go here ],})export class AppComponent { @HostBinding('@.disabled') public animationsDisabled = false; private contexts = inject(ChildrenOutletContexts); getRouteAnimationData() { return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } toggleAnimations() { this.animationsDisabled = !this.animationsDisabled; }} See all [available animation functions](https://angular.dev/guide/animations#animations-api-summary) at the end of this guide. 3. ### [Adding the animation metadata property](https://angular.dev/#adding-the-animation-metadata-property) In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property. import {Component, HostBinding, inject} from '@angular/core';import { trigger, state, style, animate, transition, // ...} from '@angular/animations';import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';import {slideInAnimation} from './animations';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'], imports: [RouterLink, RouterOutlet], animations: [ slideInAnimation, // animation triggers go here ],})export class AppComponent { @HostBinding('@.disabled') public animationsDisabled = false; private contexts = inject(ChildrenOutletContexts); getRouteAnimationData() { return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } toggleAnimations() { this.animationsDisabled = !this.animationsDisabled; }} ## [Animating a transition](https://angular.dev/#animating-a-transition) Let's animate a transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either **Open** or **Closed** based on the user's last action. When the button is in the `open` state, it's visible and yellow. When it's the `closed` state, it's translucent and blue. In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the `style()` function to specify a set of CSS styles for use with animations. Collect a set of styles in an animation state, and give the state a name, such as `open` or `closed`. **HELPFUL:** Let's create a new `open-close` component to animate with simple transitions. Run the following command in terminal to generate the component: ng g component open-close This will create the component at `src/app/open-close.component.ts`. ### [Animation state and styles](https://angular.dev/#animation-state-and-styles) Use Angular's [`state()`](https://angular.dev/api/animations/state) function to define different states to call at the end of each transition. This function takes two arguments: A unique name like `open` or `closed` and a `style()` function. Use the `style()` function to define a set of styles to associate with a given state name. You must use _camelCase_ for style attributes that contain dashes, such as `backgroundColor` or wrap them in quotes, such as `'background-color'`. Let's see how Angular's [`state()`](https://angular.dev/api/animations/state) function works with the `style()` function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a yellow background color. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} In the following `closed` state, the button has a height of 100 pixels, an opacity of 0.8, and a background color of blue. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} ### [Transitions and timing](https://angular.dev/#transitions-and-timing) In Angular, you can set multiple styles without any animation. However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring. To make the change less abrupt, you need to define an animation _transition_ to specify the changes that occur between one state and another over a period of time. The `transition()` function accepts two arguments: The first argument accepts an expression that defines the direction between two transition states, and the second argument accepts one or a series of `animate()` steps. Use the `animate()` function to define the length, delay, and easing of a transition, and to designate the style function for defining styles while transitions are taking place. Use the `animate()` function to define the `keyframes()` function for multi-step animations. These definitions are placed in the second argument of the `animate()` function. #### [Animation metadata: duration, delay, and easing](https://angular.dev/#animation-metadata-duration-delay-and-easing) The `animate()` function (second argument of the transition function) accepts the `timings` and `styles` input parameters. The `timings` parameter takes either a number or a string defined in three parts. animate (duration) or animate ('duration delay easing') The first part, `duration`, is required. The duration can be expressed in milliseconds as a number without quotes, or in seconds with quotes and a time specifier. For example, a duration of a tenth of a second can be expressed as follows: * As a plain number, in milliseconds: `100` * In a string, as milliseconds: `'100ms'` * In a string, as seconds: `'0.1s'` The second argument, `delay`, has the same syntax as `duration`. For example: * Wait for 100ms and then run for 200ms: `'0.2s 100ms'` The third argument, `easing`, controls how the animation [accelerates and decelerates](https://easings.net/) during its runtime. For example, `ease-in` causes the animation to begin slowly, and to pick up speed as it progresses. * Wait for 100ms, run for 200ms. Use a deceleration curve to start out fast and slowly decelerate to a resting point: `'0.2s 100ms ease-out'` * Run for 200ms, with no delay. Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end: `'0.2s ease-in-out'` * Start immediately, run for 200ms. Use an acceleration curve to start slow and end at full velocity: `'0.2s ease-in'` **HELPFUL:** See the Material Design website's topic on [Natural easing curves](https://material.io/design/motion/speed.html#easing) for general information on easing curves. This example provides a state transition from `open` to `closed` with a 1-second transition between states. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} In the preceding code snippet, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, `animate()` specifies how long the transition takes. In this case, the state change from `open` to `closed` takes 1 second, expressed here as `1s`. This example adds a state transition from the `closed` state to the `open` state with a 0.5-second transition animation arc. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} **HELPFUL:** Some additional notes on using styles within [`state`](https://angular.dev/api/animations/state) and `transition` functions. * Use [`state()`](https://angular.dev/api/animations/state) to define styles that are applied at the end of each transition, they persist after the animation completes * Use `transition()` to define intermediate styles, which create the illusion of motion during the animation * When animations are disabled, `transition()` styles can be skipped, but [`state()`](https://angular.dev/api/animations/state) styles can't * Include multiple state pairs within the same `transition()` argument: transition( 'on => off, off => void' ) ### [Triggering the animation](https://angular.dev/#triggering-the-animation) An animation requires a _trigger_, so that it knows when to start. The `trigger()` function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template. The `trigger()` function describes the property name to watch for changes. When a change occurs, the trigger initiates the actions included in its definition. These actions can be transitions or other functions, as we'll see later on. In this example, we'll name the trigger `openClose`, and attach it to the `button` element. The trigger describes the open and closed states, and the timings for the two transitions. **HELPFUL:** Within each `trigger()` function call, an element can only be in one state at any given time. However, it's possible for multiple triggers to be active at once. ### [Defining animations and attaching them to the HTML template](https://angular.dev/#defining-animations-and-attaching-them-to-the-html-template) Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} When you've defined an animation trigger for a component, attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state. <div [@triggerName]="expression">…</div>; The animation is executed or triggered when the expression value changes to a new state. The following code snippet binds the trigger to the value of the `isOpen` property. <nav> <button type="button" (click)="toggle()">Toggle Open/Close</button></nav><div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div> In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation. For elements entering or leaving a page (inserted or removed from the DOM), you can make the animations conditional. For example, use `*ngIf` with the animation trigger in the HTML template. **HELPFUL:** In the component file, set the trigger that defines the animations as the value of the `animations:` property in the `@Component()` decorator. In the HTML template file, use the trigger name to attach the defined animations to the HTML element to be animated. ### [Code review](https://angular.dev/#code-review) Here are the code files discussed in the transition example. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} <nav> <button type="button" (click)="toggle()">Toggle Open/Close</button></nav><div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div> :host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px;} ### [Summary](https://angular.dev/#summary) You learned to add animation to a transition between two states, using `style()` and [`state()`](https://angular.dev/api/animations/state) along with `animate()` for the timing. Learn about more advanced features in Angular animations under the Animation section, beginning with advanced techniques in [transition and triggers](https://angular.dev/guide/animations/transition-and-triggers). ## [Animations API summary](https://angular.dev/#animations-api-summary) The functional API provided by the `@angular/animations` module provides a domain-specific language (DSL) for creating and controlling animations in Angular applications. See the [API reference](https://angular.dev/api#animations) for a complete listing and syntax details of the core functions and related data structures. | Function name | What it does | | --- | --- | | `trigger()` | Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to `triggerName`. Use the first argument to declare a unique trigger name. Uses array syntax. | | `style()` | Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax. | | [`state()`](https://angular.dev/api/animations/state) | Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions. | | `animate()` | Specifies the timing information for a transition. Optional values for `delay` and `easing`. Can contain `style()` calls within. | | `transition()` | Defines the animation sequence between two named states. Uses array syntax. | | `keyframes()` | Allows a sequential change between styles within a specified time interval. Use within `animate()`. Can include multiple `style()` calls within each `keyframe()`. Uses array syntax. | | [`group()`](https://angular.dev/api/animations/group) | Specifies a group of animation steps (_inner animations_) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within `sequence()` or `transition()`. | | `query()` | Finds one or more inner HTML elements within the current element. | | `sequence()` | Specifies a list of animation steps that are run sequentially, one by one. | | `stagger()` | Staggers the starting time for animations for multiple elements. | | `animation()` | Produces a reusable animation that can be invoked from elsewhere. Used together with `useAnimation()`. | | `useAnimation()` | Activates a reusable animation. Used with `animation()`. | | `animateChild()` | Allows animations on child components to be run within the same timeframe as the parent. | ## [More on Angular animations](https://angular.dev/#more-on-angular-animations) **HELPFUL:** Check out this [presentation](https://www.youtube.com/watch?v=rnTK9meY5us), shown at the AngularConnect conference in November 2017, and the accompanying [source code](https://github.com/matsko/animationsftw.in). You might also be interested in the following: [Transition and triggers](https://angular.dev/guide/animations/transition-and-triggers) [Complex animation sequences](https://angular.dev/guide/animations/complex-sequences) [Reusable animations](https://angular.dev/guide/animations/reusable-animations) [Route transition animations](https://angular.dev/guide/animations/route-animations) --- ## Page: https://angular.dev/guide/animations/transition-and-triggers **IMPORTANT:** The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](https://angular.dev/guide/animations/migration#transition-and-triggers) to learn how you can start using pure CSS animations in your apps. This guide goes into depth on special transition states such as the `*` wildcard and `void`. It shows how these special states are used for elements entering and leaving a view. This section also explores multiple animation triggers, animation callbacks, and sequence-based animation using keyframes. ## [Predefined states and wildcard matching](https://angular.dev/#predefined-states-and-wildcard-matching) In Angular, transition states can be defined explicitly through the [`state()`](https://angular.dev/api/animations/state) function, or using the predefined `*` wildcard and `void` states. ### [Wildcard state](https://angular.dev/#wildcard-state) An asterisk `*` or _wildcard_ matches any animation state. This is useful for defining transitions that apply regardless of the HTML element's start or end state. For example, a transition of `open => *` applies when the element's state changes from open to anything else.  The following is another code sample using the wildcard state together with the previous example using the `open` and `closed` states. Instead of defining each state-to-state transition pair, any transition to `closed` takes 1 second, and any transition to `open` takes 0.5 seconds. This allows the addition of new states without having to include separate transitions for each one. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} Use a double arrow syntax to specify state-to-state transitions in both directions. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} ### [Use wildcard state with multiple transition states](https://angular.dev/#use-wildcard-state-with-multiple-transition-states) In the two-state button example, the wildcard isn't that useful because there are only two possible states, `open` and `closed`. In general, use wildcard states when an element has multiple potential states that it can change to. If the button can change from `open` to either `closed` or something like `inProgress`, using a wildcard state could reduce the amount of coding needed.  import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} The `* => *` transition applies when any change between two states takes place. Transitions are matched in the order in which they are defined. Thus, you can apply other transitions on top of the `* => *` transition. For example, define style changes or animations that would apply just to `open => closed`, then use `* => *` as a fallback for state pairings that aren't otherwise called out. To do this, list the more specific transitions _before_ `* => *`. ### [Use wildcards with styles](https://angular.dev/#use-wildcards-with-styles) Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} ### [Void state](https://angular.dev/#void-state) Use the `void` state to configure transitions for an element that is entering or leaving a page. See [Animating entering and leaving a view](https://angular.dev/guide/animations/transition-and-triggers#aliases-enter-and-leave). ### [Combine wildcard and void states](https://angular.dev/#combine-wildcard-and-void-states) Combine wildcard and void states in a transition to trigger animations that enter and leave the page: * A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left * A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering * The wildcard state `*` matches to _any_ state, including `void` ## [Animate entering and leaving a view](https://angular.dev/#animate-entering-and-leaving-a-view) This section shows how to animate elements entering or leaving a page. Add a new behavior: * When you add a hero to the list of heroes, it appears to fly onto the page from the left * When you remove a hero from the list, it appears to fly out to the right import {Component, Input, Output, EventEmitter} from '@angular/core';import {trigger, state, style, animate, transition} from '@angular/animations';import {Hero} from './hero';@Component({ selector: 'app-hero-list-enter-leave', template: ` <ul class="heroes"> @for (hero of heroes; track hero) { <li [@flyInOut]="'in'"> <button class="inner" type="button" (click)="removeHero(hero.id)"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </button> </li> } </ul> `, styleUrls: ['./hero-list-page.component.css'], animations: [ trigger('flyInOut', [ state('in', style({transform: 'translateX(0)'})), transition('void => *', [style({transform: 'translateX(-100%)'}), animate(100)]), transition('* => void', [animate(100, style({transform: 'translateX(100%)'}))]), ]), ],})export class HeroListEnterLeaveComponent { @Input() heroes: Hero[] = []; @Output() remove = new EventEmitter<number>(); removeHero(id: number) { this.remove.emit(id); }} In the preceding code, you applied the `void` state when the HTML element isn't attached to a view. ## [Aliases :enter and :leave](https://angular.dev/#aliases-enter-and-leave) `:enter` and `:leave` are aliases for the `void => *` and `* => void` transitions. These aliases are used by several animation functions. transition ( ':enter', [ … ] ); // alias for void => *transition ( ':leave', [ … ] ); // alias for * => void It's harder to target an element that is entering a view because it isn't in the DOM yet. Use the aliases `:enter` and `:leave` to target HTML elements that are inserted or removed from a view. ### [Use `*ngIf` and `*ngFor` with :enter and :leave](https://angular.dev/#use-ngif-and-ngfor-with-enter-and-leave) The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page. **IMPORTANT:** Entering/leaving behaviors can sometime be confusing. As a rule of thumb consider that any element being added to the DOM by Angular passes via the `:enter` transition. Only elements being directly removed from the DOM by Angular pass via the `:leave` transition. For example, an element's view is removed from the DOM because its parent is being removed from the DOM. This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code. <h2>Insert/Remove</h2><nav> <button type="button" (click)="toggle()">Toggle Insert/Remove</button></nav>@if (isShown) { <div @myInsertRemoveTrigger class="insert-remove-container"> <p>The box is inserted</p> </div>} In the component file, the `:enter` transition sets an initial opacity of 0. It then animates it to change that opacity to 1 as the element is inserted into the view. import {Component} from '@angular/core';import {trigger, transition, animate, style} from '@angular/animations';@Component({ selector: 'app-insert-remove', animations: [ trigger('myInsertRemoveTrigger', [ transition(':enter', [style({opacity: 0}), animate('100ms', style({opacity: 1}))]), transition(':leave', [animate('100ms', style({opacity: 0}))]), ]), ], templateUrl: 'insert-remove.component.html', styleUrls: ['insert-remove.component.css'],})export class InsertRemoveComponent { isShown = false; toggle() { this.isShown = !this.isShown; }} Note that this example doesn't need to use [`state()`](https://angular.dev/api/animations/state). ## [Transition :increment and :decrement](https://angular.dev/#transition-increment-and-decrement) The `transition()` function takes other selector values, `:increment` and `:decrement`. Use these to kick off a transition when a numeric value has increased or decreased in value. **HELPFUL:** The following example uses `query()` and `stagger()` methods. For more information on these methods, see the [complex sequences](https://angular.dev/guide/animations/complex-sequences) page. import {Component, HostBinding, OnInit} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';import {HEROES} from './mock-heroes';import {Hero} from './hero';@Component({ selector: 'app-hero-list-page', templateUrl: 'hero-list-page.component.html', styleUrls: ['hero-list-page.component.css'], animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.hero', [ style({opacity: 0, transform: 'translateY(-100px)'}), stagger(30, [ animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})), ]), ]), ]), ]), trigger('filterAnimation', [ transition(':enter, * => 0, * => -1', []), transition(':increment', [ query( ':enter', [ style({opacity: 0, width: 0}), stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]), ], {optional: true}, ), ]), transition(':decrement', [ query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]), ]), ]), ],})export class HeroListPageComponent implements OnInit { @HostBinding('@pageAnimations') public animatePage = true; heroesTotal = -1; get heroes() { return this._heroes; } private _heroes: Hero[] = []; ngOnInit() { this._heroes = HEROES; } updateCriteria(criteria: string) { criteria = criteria ? criteria.trim() : ''; this._heroes = HEROES.filter((hero) => hero.name.toLowerCase().includes(criteria.toLowerCase()), ); const newTotal = this.heroes.length; if (this.heroesTotal !== newTotal) { this.heroesTotal = newTotal; } else if (!criteria) { this.heroesTotal = -1; } }} ## [Boolean values in transitions](https://angular.dev/#boolean-values-in-transitions) If a trigger contains a Boolean value as a binding value, then this value can be matched using a `transition()` expression that compares `true` and `false`, or `1` and `0`. <nav> <button type="button" (click)="toggle()">Toggle Boolean/Close</button></nav><div [@openClose]="isOpen ? true : false" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div> In the code snippet above, the HTML template binds a `<div>` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This pattern is an alternative to the practice of creating two named states like `open` and `close`. Inside the `@Component` metadata under the `animations:` property, when the state evaluates to `true`, the associated HTML element's height is a wildcard style or default. In this case, the animation uses whatever height the element already had before the animation started. When the element is `closed`, the element gets animated to a height of 0, which makes it invisible. import {Component} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close-boolean', animations: [ trigger('openClose', [ state('true', style({height: '*'})), state('false', style({height: '0px'})), transition('false <=> true', animate(500)), ]), ], templateUrl: 'open-close.component.2.html', styleUrls: ['open-close.component.css'],})export class OpenCloseBooleanComponent { isOpen = false; toggle() { this.isOpen = !this.isOpen; }} ## [Multiple animation triggers](https://angular.dev/#multiple-animation-triggers) You can define more than one animation trigger for a component. Attach animation triggers to different elements, and the parent-child relationships among the elements affect how and when the animations run. ### [Parent-child animations](https://angular.dev/#parent-child-animations) Each time an animation is triggered in Angular, the parent animation always gets priority and child animations are blocked. For a child animation to run, the parent animation must query each of the elements containing child animations. It then lets the animations run using the [`animateChild()`](https://angular.dev/api/animations/animateChild) function. #### [Disable an animation on an HTML element](https://angular.dev/#disable-an-animation-on-an-html-element) A special animation control binding called `@.disabled` can be placed on an HTML element to turn off animations on that element, as well as any nested elements. When true, the `@.disabled` binding prevents all animations from rendering. The following code sample shows how to use this feature. <nav> <button type="button" (click)="toggleAnimations()">Toggle Animations</button> <button type="button" (click)="toggle()">Toggle Open/Closed</button></nav><div [@.disabled]="isDisabled"> <div [@childAnimation]="isOpen ? 'open' : 'closed'" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p> </div></div> import {Component} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close-toggle', templateUrl: 'open-close.component.4.html', styleUrls: ['open-close.component.css'], animations: [ trigger('childAnimation', [ // ... state( 'open', style({ width: '250px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ width: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('* => *', [animate('1s')]), ]), ],})export class OpenCloseChildComponent { isDisabled = false; isOpen = false; toggleAnimations() { this.isDisabled = !this.isDisabled; } toggle() { this.isOpen = !this.isOpen; }} When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off. When an element within an HTML template has animations turned off using the `@.disabled` host binding, animations are turned off on all inner elements as well. You can't selectively turn off multiple animations on a single element. A selective child animations can still be run on a disabled parent in one of the following ways: * A parent animation can use the [`query()`](https://angular.dev/api/animations/query) function to collect inner elements located in disabled areas of the HTML template. Those elements can still animate. * A child animation can be queried by a parent and then later animated with the `animateChild()` function #### [Disable all animations](https://angular.dev/#disable-all-animations) To turn off all animations for an Angular application, place the `@.disabled` host binding on the topmost Angular component. import {Component, HostBinding, inject} from '@angular/core';import { trigger, state, style, animate, transition, // ...} from '@angular/animations';import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';import {slideInAnimation} from './animations';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'], imports: [RouterLink, RouterOutlet], animations: [ slideInAnimation, // animation triggers go here ],})export class AppComponent { @HostBinding('@.disabled') public animationsDisabled = false; private contexts = inject(ChildrenOutletContexts); getRouteAnimationData() { return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation']; } toggleAnimations() { this.animationsDisabled = !this.animationsDisabled; }} **HELPFUL:** Disabling animations application-wide is useful during end-to-end (E2E) testing. ## [Animation callbacks](https://angular.dev/#animation-callbacks) The animation `trigger()` function emits _callbacks_ when it starts and when it finishes. The following example features a component that contains an `openClose` trigger. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} In the HTML template, the animation event is passed back via `$event`, as `@triggerName.start` and `@triggerName.done`, where `triggerName` is the name of the trigger being used. In this example, the trigger `openClose` appears as follows. <nav> <button type="button" (click)="toggle()">Toggle Open/Close</button></nav> <div [@openClose]="isOpen ? 'open' : 'closed'" (@openClose.start)="onAnimationEvent($event)" (@openClose.done)="onAnimationEvent($event)" class="open-close-container"> <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p></div> A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, an **InProgress** button can be set up to have its own looping animation while the backend system operation finishes. Another animation can be called when the current animation finishes. For example, the button goes from the `inProgress` state to the `closed` state when the API call is completed. An animation can influence an end user to _perceive_ the operation as faster, even when it is not. Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console. The following code snippet creates console log output for the original example, a button with the two states of `open` and `closed`. import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} ## [Keyframes](https://angular.dev/#keyframes) To create an animation with multiple steps run in sequence, use _keyframes_. Angular's `keyframe()` function allows several style changes within a single timing segment. For example, the button, instead of fading, could change color several times over a single 2-second time span.  The code for this color change might look like this. import {Component} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-status-slider', templateUrl: 'status-slider.component.html', styleUrls: ['status-slider.component.css'], animations: [ trigger('slideStatus', [ state('inactive', style({backgroundColor: 'blue'})), state('active', style({backgroundColor: '#754600'})), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue', offset: 0}), style({backgroundColor: 'red', offset: 0.8}), style({backgroundColor: '#754600', offset: 1.0}), ]), ), ]), transition('* => inactive', [ animate( '2s', keyframes([ style({backgroundColor: '#754600', offset: 0}), style({backgroundColor: 'red', offset: 0.2}), style({backgroundColor: 'blue', offset: 1.0}), ]), ), ]), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue'}), style({backgroundColor: 'red'}), style({backgroundColor: 'orange'}), ]), ), ]), ]), ],})export class StatusSliderComponent { status: 'active' | 'inactive' = 'inactive'; toggle() { if (this.status === 'active') { this.status = 'inactive'; } else { this.status = 'active'; } }} ### [Offset](https://angular.dev/#offset) Keyframes include an `offset` that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation. They should be applied to each of the keyframe steps if used at least once. Defining offsets for keyframes is optional. If you omit them, evenly spaced offsets are automatically assigned. For example, three keyframes without predefined offsets receive offsets of 0, 0.5, and 1. Specifying an offset of 0.8 for the middle transition in the preceding example might look like this.  The code with offsets specified would be as follows. import {Component} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-status-slider', templateUrl: 'status-slider.component.html', styleUrls: ['status-slider.component.css'], animations: [ trigger('slideStatus', [ state('inactive', style({backgroundColor: 'blue'})), state('active', style({backgroundColor: '#754600'})), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue', offset: 0}), style({backgroundColor: 'red', offset: 0.8}), style({backgroundColor: '#754600', offset: 1.0}), ]), ), ]), transition('* => inactive', [ animate( '2s', keyframes([ style({backgroundColor: '#754600', offset: 0}), style({backgroundColor: 'red', offset: 0.2}), style({backgroundColor: 'blue', offset: 1.0}), ]), ), ]), transition('* => active', [ animate( '2s', keyframes([ style({backgroundColor: 'blue'}), style({backgroundColor: 'red'}), style({backgroundColor: 'orange'}), ]), ), ]), ]), ],})export class StatusSliderComponent { status: 'active' | 'inactive' = 'inactive'; toggle() { if (this.status === 'active') { this.status = 'inactive'; } else { this.status = 'active'; } }} You can combine keyframes with `duration`, `delay`, and `easing` within a single animation. ### [Keyframes with a pulsation](https://angular.dev/#keyframes-with-a-pulsation) Use keyframes to create a pulse effect in your animations by defining styles at specific offset throughout the animation. Here's an example of using keyframes to create a pulse effect: * The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second * A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1 second timeframe  The code snippet for this animation might look like this. import {Component, Input} from '@angular/core';import { trigger, transition, state, animate, style, keyframes, AnimationEvent,} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'close', style({ height: '100px', opacity: 0.5, backgroundColor: 'green', }), ), // ... transition('* => *', [ animate( '1s', keyframes([ style({opacity: 0.1, offset: 0.1}), style({opacity: 0.6, offset: 0.2}), style({opacity: 1, offset: 0.5}), style({opacity: 0.2, offset: 0.7}), ]), ), ]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseKeyframeComponent { isOpen = false; toggle() { this.isOpen = !this.isOpen; } @Input() logging = false; onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } }} ### [Animatable properties and units](https://angular.dev/#animatable-properties-and-units) Angular animations support builds on top of web animations, so you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1) page. For properties with a numeric value, define a unit by providing the value as a string, in quotes, with the appropriate suffix: * 50 pixels: `'50px'` * Relative font size: `'3em'` * Percentage: `'100%'` You can also provide the value as a number. In such cases Angular assumes a default unit of pixels, or `px`. Expressing 50 pixels as `50` is the same as saying `'50px'`. **HELPFUL:** The string `"50"` would instead not be considered valid). ### [Automatic property calculation with wildcards](https://angular.dev/#automatic-property-calculation-with-wildcards) Sometimes, the value of a dimensional style property isn't known until runtime. For example, elements often have widths and heights that depend on their content or the screen size. These properties are often challenging to animate using CSS. In these cases, you can use a special wildcard `*` property value under `style()`. The value of that particular style property is computed at runtime and then plugged into the animation. The following example has a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero. import {Component, Input, Output, EventEmitter} from '@angular/core';import {trigger, state, style, animate, transition} from '@angular/animations';import {Hero} from './hero';@Component({ selector: 'app-hero-list-auto', templateUrl: 'hero-list-auto.component.html', styleUrls: ['./hero-list-page.component.css'], animations: [ trigger('shrinkOut', [ state('in', style({height: '*'})), transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]), ]), ],})export class HeroListAutoComponent { @Input() heroes: Hero[] = []; @Output() remove = new EventEmitter<number>(); removeHero(id: number) { this.remove.emit(id); }} ### [Keyframes summary](https://angular.dev/#keyframes-summary) The `keyframes()` function in Angular allows you to specify multiple interim styles within a single transition. An optional `offset` can be used to define the point in the animation where each style change should occur. ## [More on Angular animations](https://angular.dev/#more-on-angular-animations) You might also be interested in the following: [Introduction to Angular animations](https://angular.dev/guide/animations) [Complex animation sequences](https://angular.dev/guide/animations/complex-sequences) [Reusable animations](https://angular.dev/guide/animations/reusable-animations) [Route transition animations](https://angular.dev/guide/animations/route-animations) --- ## Page: https://angular.dev/guide/animations/complex-sequences **IMPORTANT:** The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](https://angular.dev/guide/animations/migration#complex-sequences) to learn how you can start using pure CSS animations in your apps. So far, we've learned simple animations of single HTML elements. Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page. You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another. The functions that control complex animation sequences are: | Functions | Details | | --- | --- | | `query()` | Finds one or more inner HTML elements. | | `stagger()` | Applies a cascading delay to animations for multiple elements. | | [`group()`](https://angular.dev/api/animations/group) | Runs multiple animation steps in parallel. | | `sequence()` | Runs animation steps one after another. | ## [The query() function](https://angular.dev/#the-query-function) Most complex animations rely on the `query()` function to find child elements and apply animations to them, basic examples of such are: | Examples | Details | | --- | --- | | `query()` followed by `animate()` | Used to query simple HTML elements and directly apply animations to them. | | `query()` followed by `animateChild()` | Used to query child elements, which themselves have animations metadata applied to them and trigger such animation (which would be otherwise be blocked by the current/parent element's animation). | The first argument of `query()` is a [css selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) string which can also contain the following Angular-specific tokens: | Tokens | Details | | --- | --- | | `:enter` `:leave` | For entering/leaving elements. | | `:animating` | For elements currently animating. | | `@*` `@triggerName` | For elements with any—or a specific—trigger. | | `:self` | The animating element itself. | ### Entering and Leaving Elements Not all child elements are actually considered as entering/leaving; this can, at times, be counterintuitive and confusing. Please see the [query api docs](https://angular.dev/api/animations/query#entering-and-leaving-elements) for more information. You can also see an illustration of this in the animations example (introduced in the animations [introduction section](https://angular.dev/guide/animations#about-this-guide)) under the Querying tab. ## [Animate multiple elements using query() and stagger() functions](https://angular.dev/#animate-multiple-elements-using-query-and-stagger-functions) After having queried child elements via `query()`, the `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them. The following example demonstrates how to use the `query()` and `stagger()` functions to animate a list (of heroes) adding each in sequence, with a slight delay, from top to bottom. * Use `query()` to look for an element entering the page that meets certain criteria * For each of these elements, use `style()` to set the same initial style for the element. Make it transparent and use `transform` to move it out of position so that it can slide into place. * Use `stagger()` to delay each animation by 30 milliseconds * Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it import {Component, HostBinding, OnInit} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';import {HEROES} from './mock-heroes';import {Hero} from './hero';@Component({ selector: 'app-hero-list-page', templateUrl: 'hero-list-page.component.html', styleUrls: ['hero-list-page.component.css'], animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.hero', [ style({opacity: 0, transform: 'translateY(-100px)'}), stagger(30, [ animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})), ]), ]), ]), ]), trigger('filterAnimation', [ transition(':enter, * => 0, * => -1', []), transition(':increment', [ query( ':enter', [ style({opacity: 0, width: 0}), stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]), ], {optional: true}, ), ]), transition(':decrement', [ query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]), ]), ]), ],})export class HeroListPageComponent implements OnInit { @HostBinding('@pageAnimations') public animatePage = true; heroesTotal = -1; get heroes() { return this._heroes; } private _heroes: Hero[] = []; ngOnInit() { this._heroes = HEROES; } updateCriteria(criteria: string) { criteria = criteria ? criteria.trim() : ''; this._heroes = HEROES.filter((hero) => hero.name.toLowerCase().includes(criteria.toLowerCase()), ); const newTotal = this.heroes.length; if (this.heroesTotal !== newTotal) { this.heroesTotal = newTotal; } else if (!criteria) { this.heroesTotal = -1; } }} ## [Parallel animation using group() function](https://angular.dev/#parallel-animation-using-group-function) You've seen how to add a delay between each successive animation. But you might also want to configure animations that happen in parallel. For example, you might want to animate two CSS properties of the same element but use a different `easing` function for each one. For this, you can use the animation [`group()`](https://angular.dev/api/animations/group) function. **HELPFUL:** The [`group()`](https://angular.dev/api/animations/group) function is used to group animation _steps_, rather than animated elements. The following example uses [`group()`](https://angular.dev/api/animations/group)s on both `:enter` and `:leave` for two different timing configurations, thus applying two independent animations to the same element in parallel. import {Component, Input, Output, EventEmitter} from '@angular/core';import {trigger, state, style, animate, transition, group} from '@angular/animations';import {Hero} from './hero';@Component({ selector: 'app-hero-list-groups', template: ` <ul class="heroes"> @for (hero of heroes; track hero) { <li [@flyInOut]="'in'"> <button class="inner" type="button" (click)="removeHero(hero.id)"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </button> </li> } </ul> `, styleUrls: ['./hero-list-page.component.css'], animations: [ trigger('flyInOut', [ state( 'in', style({ width: '*', transform: 'translateX(0)', opacity: 1, }), ), transition(':enter', [ style({width: 10, transform: 'translateX(50px)', opacity: 0}), group([ animate( '0.3s 0.1s ease', style({ transform: 'translateX(0)', width: '*', }), ), animate( '0.3s ease', style({ opacity: 1, }), ), ]), ]), transition(':leave', [ group([ animate( '0.3s ease', style({ transform: 'translateX(50px)', width: 10, }), ), animate( '0.3s 0.2s ease', style({ opacity: 0, }), ), ]), ]), ]), ],})export class HeroListGroupsComponent { @Input() heroes: Hero[] = []; @Output() remove = new EventEmitter<number>(); removeHero(id: number) { this.remove.emit(id); }} ## [Sequential vs. parallel animations](https://angular.dev/#sequential-vs-parallel-animations) Complex animations can have many things happening at once. But what if you want to create an animation involving several animations happening one after the other? Earlier you used [`group()`](https://angular.dev/api/animations/group) to run multiple animations all at the same time, in parallel. A second function called `sequence()` lets you run those same animations one after the other. Within `sequence()`, the animation steps consist of either `style()` or `animate()` function calls. * Use `style()` to apply the provided styling data immediately. * Use `animate()` to apply styling data over a given time interval. ## [Filter animation example](https://angular.dev/#filter-animation-example) Take a look at another animation on the example page. Under the Filter/Stagger tab, enter some text into the **Search Heroes** text box, such as `Magnet` or `tornado`. The filter works in real time as you type. Elements leave the page as you type each new letter and the filter gets progressively stricter. The heroes list gradually re-enters the page as you delete each letter in the filter box. The HTML template contains a trigger called `filterAnimation`. <h2>Filter/Stagger</h2><label for="search">Search heroes: </label><input type="text" id="search" #criteria (input)="updateCriteria(criteria.value)" placeholder="Search heroes"><ul class="heroes" [@filterAnimation]="heroesTotal"> @for (hero of heroes; track hero) { <li class="hero"> <div class="inner"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </div> </li> }</ul> The `filterAnimation` in the component's decorator contains three transitions. import {Component, HostBinding, OnInit} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';import {HEROES} from './mock-heroes';import {Hero} from './hero';@Component({ selector: 'app-hero-list-page', templateUrl: 'hero-list-page.component.html', styleUrls: ['hero-list-page.component.css'], animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.hero', [ style({opacity: 0, transform: 'translateY(-100px)'}), stagger(30, [ animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})), ]), ]), ]), ]), trigger('filterAnimation', [ transition(':enter, * => 0, * => -1', []), transition(':increment', [ query( ':enter', [ style({opacity: 0, width: 0}), stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]), ], {optional: true}, ), ]), transition(':decrement', [ query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]), ]), ]), ],})export class HeroListPageComponent implements OnInit { @HostBinding('@pageAnimations') public animatePage = true; heroesTotal = -1; get heroes() { return this._heroes; } private _heroes: Hero[] = []; ngOnInit() { this._heroes = HEROES; } updateCriteria(criteria: string) { criteria = criteria ? criteria.trim() : ''; this._heroes = HEROES.filter((hero) => hero.name.toLowerCase().includes(criteria.toLowerCase()), ); const newTotal = this.heroes.length; if (this.heroesTotal !== newTotal) { this.heroesTotal = newTotal; } else if (!criteria) { this.heroesTotal = -1; } }} The code in this example performs the following tasks: * Skips animations when the user first opens or navigates to this page (the filter animation narrows what is already there, so it only works on elements that already exist in the DOM) * Filters heroes based on the search input's value For each change: * Hides an element leaving the DOM by setting its opacity and width to 0 * Animates an element entering the DOM over 300 milliseconds. During the animation, the element assumes its default width and opacity. * If there are multiple elements entering or leaving the DOM, staggers each animation starting at the top of the page, with a 50-millisecond delay between each element ## [Animating the items of a reordering list](https://angular.dev/#animating-the-items-of-a-reordering-list) Although Angular animates correctly `*ngFor` list items out of the box, it will not be able to do so if their ordering changes. This is because it will lose track of which element is which, resulting in broken animations. The only way to help Angular keep track of such elements is by assigning a `TrackByFunction` to the `NgForOf` directive. This makes sure that Angular always knows which element is which, thus allowing it to apply the correct animations to the correct elements all the time. **IMPORTANT:** If you need to animate the items of an `*ngFor` list and there is a possibility that the order of such items will change during runtime, always use a `TrackByFunction`. ## [Animations and Component View Encapsulation](https://angular.dev/#animations-and-component-view-encapsulation) Angular animations are based on the components DOM structure and do not directly take [View Encapsulation](https://angular.dev/guide/components/styling#style-scoping) into account, this means that components using `ViewEncapsulation.Emulated` behave exactly as if they were using `ViewEncapsulation.None` (`ViewEncapsulation.ShadowDom` behaves differently as we'll discuss shortly). For example if the `query()` function (which you'll see more of in the rest of the Animations guide) were to be applied at the top of a tree of components using the emulated view encapsulation, such query would be able to identify (and thus animate) DOM elements on any depth of the tree. On the other hand the `ViewEncapsulation.ShadowDom` changes the component's DOM structure by "hiding" DOM elements inside [`ShadowRoot`](https://developer.mozilla.org/docs/Web/API/ShadowRoot) elements. Such DOM manipulations do prevent some of the animations implementation to work properly since it relies on simple DOM structures and doesn't take `ShadowRoot` elements into account. Therefore it is advised to avoid applying animations to views incorporating components using the ShadowDom view encapsulation. ## [Animation sequence summary](https://angular.dev/#animation-sequence-summary) Angular functions for animating multiple elements start with `query()` to find inner elements; for example, gathering all images within a `<div>`. The remaining functions, `stagger()`, [`group()`](https://angular.dev/api/animations/group), and `sequence()`, apply cascades or let you control how multiple animation steps are applied. ## [More on Angular animations](https://angular.dev/#more-on-angular-animations) You might also be interested in the following: [Introduction to Angular animations](https://angular.dev/guide/animations) [Transition and triggers](https://angular.dev/guide/animations/transition-and-triggers) [Reusable animations](https://angular.dev/guide/animations/reusable-animations) [Route transition animations](https://angular.dev/guide/animations/route-animations) --- ## Page: https://angular.dev/guide/animations/reusable-animations **IMPORTANT:** The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](https://angular.dev/guide/animations/migration#creating-reusable-animations) to learn how you can start using pure CSS animations in your apps. This topic provides some examples of how to create reusable animations. ## [Create reusable animations](https://angular.dev/#create-reusable-animations) To create a reusable animation, use the [`animation()`](https://angular.dev/api/animations/animation) function to define an animation in a separate `.ts` file and declare this animation definition as a `const` export variable. You can then import and reuse this animation in any of your application components using the [`useAnimation()`](https://angular.dev/api/animations/useAnimation) function. import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';export const transitionAnimation = animation([ style({ height: '{{ height }}', opacity: '{{ opacity }}', backgroundColor: '{{ backgroundColor }}', }), animate('{{ time }}'),]);export const sharedAnimation = animation([ style({ height: 0, opacity: 1, backgroundColor: 'red', }), animate('1s'),]);export const triggerAnimation = trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]),]); In the preceding code snippet, `transitionAnimation` is made reusable by declaring it as an export variable. **HELPFUL:** The `height`, `opacity`, `backgroundColor`, and `time` inputs are replaced during runtime. You can also export a part of an animation. For example, the following snippet exports the animation `trigger`. import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';export const transitionAnimation = animation([ style({ height: '{{ height }}', opacity: '{{ opacity }}', backgroundColor: '{{ backgroundColor }}', }), animate('{{ time }}'),]);export const sharedAnimation = animation([ style({ height: 0, opacity: 1, backgroundColor: 'red', }), animate('1s'),]);export const triggerAnimation = trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]),]); From this point, you can import reusable animation variables in your component class. For example, the following code snippet imports the `transitionAnimation` variable and uses it via the `useAnimation()` function. import {Component, Input} from '@angular/core';import {transition, trigger, useAnimation, AnimationEvent} from '@angular/animations';import {transitionAnimation} from './animations';@Component({ selector: 'app-open-close-reusable', animations: [ trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseBooleanComponent { isOpen = false; toggle() { this.isOpen = !this.isOpen; } @Input() logging = false; onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } }} ## [More on Angular animations](https://angular.dev/#more-on-angular-animations) You might also be interested in the following: [Introduction to Angular animations](https://angular.dev/guide/animations) [Transition and triggers](https://angular.dev/guide/animations/transition-and-triggers) [Complex animation sequences](https://angular.dev/guide/animations/complex-sequences) [Route transition animations](https://angular.dev/guide/animations/route-animations) --- ## Page: https://angular.dev/guide/animations/migration Almost all the features supported by `@angular/animations` have simpler alternatives with native CSS. Consider removing the Angular Animations package from your application, as the package can contribute around 60 kilobytes to your JavaScript bundle. Native CSS animations offer superior performance, as they can benefit from hardware acceleration. Animations defined in the animations package lack that ability. This guide walks through the process of refactoring your code from `@angular/animations` to native CSS animations. ## [How to write animations in native CSS](https://angular.dev/#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](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) [W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp) [The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/) [CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners) and a couple of videos: [Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw) [Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5) Check some of these various guides and tutorials out, and then come back to this guide. ## [Creating Reusable Animations](https://angular.dev/#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](https://angular.dev/#with-animations-package) import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';export const transitionAnimation = animation([ style({ height: '{{ height }}', opacity: '{{ opacity }}', backgroundColor: '{{ backgroundColor }}', }), animate('{{ time }}'),]);export const sharedAnimation = animation([ style({ height: 0, opacity: 1, backgroundColor: 'red', }), animate('1s'),]);export const triggerAnimation = trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]),]); #### [With Native CSS](https://angular.dev/#with-native-css) @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Adding the class `animated-class` to an element would trigger the animation on that element. ## [Animating a Transition](https://angular.dev/#animating-a-transition) ### [Animating State and Styles](https://angular.dev/#animating-state-and-styles) The animations package allowed you to define various states using the [`state()`](https://angular.dev/api/animations/state) function within a component. Examples might be an `open` or `closed` state containing the styles for each respective state within the definition. For example: #### [With Animations Package](https://angular.dev/#with-animations-package-1) import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ // ... state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }} This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling. #### [With Native CSS](https://angular.dev/#with-native-css-1) @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Triggering 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](https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings). You can see similar examples in the template guide for [animating styles directly](https://angular.dev/guide/templates/binding#css-style-properties). ### [Transitions, Timing, and Easing](https://angular.dev/#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. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`. @keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.example-element { transition-duration: 1s; transition-delay: 500ms; transition-timing-function: ease-in-out; transition-property: margin-right;}.example-shorthand { transition: margin-right 1s ease-in-out 500ms;} ### [Triggering an Animation](https://angular.dev/#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](https://angular.dev/#with-animations-package-2) import {Component, signal} from '@angular/core';import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ state( 'open', style({ height: '200px', opacity: 1, backgroundColor: 'yellow', }), ), state( 'closed', style({ height: '100px', opacity: 0.5, backgroundColor: 'green', }), ), // ... transition('* => *', [ animate( '1s', keyframes([ style({opacity: 0.1, offset: 0.1}), style({opacity: 0.6, offset: 0.2}), style({opacity: 1, offset: 0.5}), style({opacity: 0.2, offset: 0.7}), ]), ), ]), ]), ], templateUrl: 'open-close.component.html', styleUrl: 'open-close.component.css',})export class OpenCloseComponent { isOpen = signal(false); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <nav> <button type="button" (click)="toggle()">Toggle Open/Close</button></nav><div [@openClose]="isOpen() ? 'open' : 'closed'" class="open-close-container"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p></div> :host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px;} #### [With Native CSS](https://angular.dev/#with-native-css-2) import {Component, signal} from '@angular/core';@Component({ selector: 'app-open-close', templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <h2>Open / Close Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="open-close-container" [class.open]="isOpen()"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p></div> :host { display: block; margin-top: 1rem;}.open-close-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; font-weight: bold; font-size: 20px; height: 100px; opacity: 0.8; background-color: blue; color: #ebebeb; transition-property: height, opacity, background-color, color; transition-duration: 1s;}.open { transition-duration: 0.5s; height: 200px; opacity: 1; background-color: yellow; color: #000000;} ## [Transition and Triggers](https://angular.dev/#transition-and-triggers) ### [Predefined State and wildcard matching](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#with-animations-package-3) import {Component, signal} from '@angular/core';import {trigger, transition, state, animate, style} from '@angular/animations';@Component({ selector: 'app-open-close', animations: [ trigger('openClose', [ state('true', style({height: '*'})), state('false', style({height: '0px'})), transition('false <=> true', animate(1000)), ]), ], templateUrl: 'auto-height.component.html', styleUrl: 'auto-height.component.css',})export class AutoHeightComponent { isOpen = signal(false); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <h2>Auto Height Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="container" [@openClose]="isOpen() ? true : false"> <div class="content"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p> </div></div> .container { display: block; overflow: hidden;}.container .content { padding: 20px; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb;} You can use css-grid to animate to auto height. #### [With Native CSS](https://angular.dev/#with-native-css-3) import {Component, signal} from '@angular/core';@Component({ selector: 'app-auto-height', templateUrl: 'auto-height.component.html', styleUrls: ['auto-height.component.css'],})export class AutoHeightComponent { isOpen = signal(true); toggle() { this.isOpen.update((isOpen) => !isOpen); }} <h2>Auto Height Example</h2><button type="button" (click)="toggle()">Toggle Open/Close</button><div class="container" [class.open]="isOpen()"> <div class="content"> <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p> </div></div> .container { display: grid; grid-template-rows: 0fr; overflow: hidden; transition: grid-template-rows 1s;}.container.open { grid-template-rows: 1fr;}.container .content { min-height: 0; transition: visibility 1s; padding: 0 20px; visibility: hidden; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb; overflow: hidden;}.container.open .content { visibility: visible;} If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)\[[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/\]](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](https://angular.dev/#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](https://angular.dev/#with-animations-package-4) import {Component} from '@angular/core';import {trigger, transition, animate, style} from '@angular/animations';@Component({ selector: 'app-insert-remove', animations: [ trigger('myInsertRemoveTrigger', [ transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]), transition(':leave', [animate('200ms', style({opacity: 0}))]), ]), ], templateUrl: 'insert-remove.component.html', styleUrls: ['insert-remove.component.css'],})export class InsertRemoveComponent { isShown = false; toggle() { this.isShown = !this.isShown; }} <h2>Insert/Remove</h2><nav> <button type="button" (click)="toggle()">Toggle Insert/Remove</button></nav>@if (isShown) { <div @myInsertRemoveTrigger class="insert-remove-container"> <p>The box is inserted</p> </div>} :host { display: block;}.insert-remove-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px;} Here's how the same thing can be accomplished without the animations package. #### [With Native CSS](https://angular.dev/#with-native-css-4) import {Component, signal} from '@angular/core';@Component({ selector: 'app-insert', templateUrl: 'insert.component.html', styleUrls: ['insert.component.css'],})export class InsertComponent { isShown = signal(false); toggle() { this.isShown.update((isShown) => !isShown); }} <h2>Insert Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container"> <p>The box is inserted</p> </div>} :host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px; opacity: 1; transition: opacity 1s ease-out, transform 1s ease-out; @starting-style { opacity: 0; transform: translateY(20px); }} Leaving a view is slightly more complex. The element removal needs to be delayed until the exit animation is complete. This requires a bit of extra code in your component class to accomplish. #### [With Native CSS](https://angular.dev/#with-native-css-5) import {Component, ElementRef, inject, signal} from '@angular/core';@Component({ selector: 'app-remove', templateUrl: 'remove.component.html', styleUrls: ['remove.component.css'],})export class RemoveComponent { isShown = signal(false); deleting = signal(false); private el = inject(ElementRef); toggle() { if (this.isShown()) { const target = this.el.nativeElement.querySelector('.insert-container'); target.addEventListener('transitionend', () => this.hide()); this.deleting.set(true); } else { this.isShown.update((isShown) => !isShown); } } hide() { this.isShown.set(false); this.deleting.set(false); }} <h2>Remove Element Example</h2><nav> <button type="button" (click)="toggle()">Toggle Element</button></nav>@if (isShown()) { <div class="insert-container" [class.deleting]="deleting()"> <p>The box is inserted</p> </div>} :host { display: block;}.insert-container { border: 1px solid #dddddd; margin-top: 1em; padding: 20px 20px 0px 20px; color: #000000; font-weight: bold; font-size: 20px; opacity: 1; transition: opacity 200ms ease-in; @starting-style { opacity: 0; }}.deleting { opacity: 0; transform: translateY(20px); transition: opacity 500ms ease-out, transform 500ms ease-out;} ### [Animating increment and decrement](https://angular.dev/#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](https://angular.dev/#with-animations-package-5) import {Component, signal} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';@Component({ selector: 'app-increment-decrement', templateUrl: 'increment-decrement.component.html', styleUrls: ['increment-decrement.component.css'], animations: [ trigger('incrementAnimation', [ transition(':increment', [ animate('300ms ease-out', style({color: 'green', transform: 'scale(1.3, 1.2)'})), ]), transition(':decrement', [ animate('300ms ease-out', style({color: 'red', transform: 'scale(0.8, 0.9)'})), ]), ]), ],})export class IncrementDecrementComponent { num = signal(0); modify(n: number) { this.num.update((v) => (v += n)); }} <h3>Increment and Decrement Example</h3><section> <p [@incrementAnimation]="num()">Number {{ num() }}</p> <div class="controls"> <button type="button" (click)="modify(1)">+</button> <button type="button" (click)="modify(-1)">-</button> </div></section> :host { display: block; font-size: 32px; margin: 20px; text-align: center;}section { border: 1px solid lightgray; border-radius: 50px;}p { display: inline-block; margin: 2rem 0; text-transform: uppercase;}.controls { padding-bottom: 2rem;}button { font: inherit; border: 0; background: lightgray; width: 50px; border-radius: 10px;}button + button { margin-left: 10px;} #### [With Native CSS](https://angular.dev/#with-native-css-6) import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';@Component({ selector: 'app-increment-decrement', templateUrl: 'increment-decrement.component.html', styleUrls: ['increment-decrement.component.css'],})export class IncrementDecrementComponent implements OnInit { num = signal(0); el = viewChild<ElementRef<HTMLParagraphElement>>('el'); ngOnInit() { this.el()?.nativeElement.addEventListener('animationend', (ev) => { if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) { this.animationFinished(); } }); } modify(n: number) { const targetClass = n > 0 ? 'increment' : 'decrement'; this.num.update((v) => (v += n)); this.el()?.nativeElement.classList.add(targetClass); } animationFinished() { this.el()?.nativeElement.classList.remove('increment', 'decrement'); } ngOnDestroy() { this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished); }} <h3>Increment and Decrement Example</h3><section> <p #el>Number {{ num() }}</p> <div class="controls"> <button type="button" (click)="modify(1)">+</button> <button type="button" (click)="modify(-1)">-</button> </div></section> :host { display: block; font-size: 32px; margin: 20px; text-align: center;}section { border: 1px solid lightgray; border-radius: 50px;}p { display: inline-block; margin: 2rem 0; text-transform: uppercase;}.increment { animation: increment 300ms;}.decrement { animation: decrement 300ms;}.controls { padding-bottom: 2rem;}button { font: inherit; border: 0; background: lightgray; width: 50px; border-radius: 10px;}button + button { margin-left: 10px;}@keyframes increment { 33% { color: green; transform: scale(1.3, 1.2); } 66% { color: green; transform: scale(1.2, 1.2); } 100% { transform: scale(1, 1); }}@keyframes decrement { 33% { color: red; transform: scale(0.8, 0.9); } 66% { color: red; transform: scale(0.9, 0.9); } 100% { transform: scale(1, 1); }} ### [Parent / Child Animations](https://angular.dev/#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](https://angular.dev/#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`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation. 2. Prevent adding animation classes programatically ### [Animation Callbacks](https://angular.dev/#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`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event) [`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event) [`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event) [`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event) [`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event) [`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event) [`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event) [`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event) The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) 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](https://angular.dev/#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](https://angular.dev/#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()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). This is unnecessary in a native CSS animation world. Instead, you can use your CSS selectors to target sub-classes and apply any desired `transform` or `animation`. To toggle classes for child nodes within a template, you can use class and style bindings to add the animations at the right points. ### [Stagger()](https://angular.dev/#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](https://angular.dev/#with-animations-package-6) import {Component, HostBinding, signal} from '@angular/core';import {trigger, transition, animate, style, query, stagger} from '@angular/animations';@Component({ selector: 'app-stagger', templateUrl: 'stagger.component.html', styleUrls: ['stagger.component.css'], animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.item', [ style({opacity: 0, transform: 'translateY(-10px)'}), stagger(200, [animate('500ms ease-in', style({opacity: 1, transform: 'none'}))]), ]), ]), ]), ],})export class StaggerComponent { @HostBinding('@pageAnimations') items = [1, 2, 3];} <h2>Stagger Example</h2><ul class="items"> @for(item of items; track item) { <li class="item">{{ item }}</li> }</ul> .items { list-style: none; padding: 0; margin: 0;} #### [With Native CSS](https://angular.dev/#with-native-css-7) import {Component, signal} from '@angular/core';@Component({ selector: 'app-stagger', templateUrl: './stagger.component.html', styleUrls: ['stagger.component.css'],})export class StaggerComponent { show = signal(true); items = [1, 2, 3]; refresh() { this.show.set(false); setTimeout(() => { this.show.set(true); }, 10); }} <h1>Stagger Example</h1><button type="button" (click)="refresh()">Refresh</button>@if (show()) { <ul class="items"> @for(item of items; track item) { <li class="item" style="--index: {{ item }}">{{item}}</li> } </ul>} .items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; transition-delay: calc(200ms * var(--index)); @starting-style { opacity: 0; transform: translateX(-10px); }} ### [Parallel Animations](https://angular.dev/#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](https://angular.dev/#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. Removal animations will require additional code to add the event listener, as seen in the example above. #### [With Animations Package<](https://angular.dev/#with-animations-package) import {Component, signal} from '@angular/core';import {trigger, transition, animate, query, style} from '@angular/animations';@Component({ selector: 'app-reorder', templateUrl: './reorder.component.html', styleUrls: ['reorder.component.css'], animations: [ trigger('itemAnimation', [ transition(':enter', [ style({opacity: 0, transform: 'translateX(-10px)'}), animate('300ms', style({opacity: 1, transform: 'translateX(none)'})), ]), transition(':leave', [ style({opacity: 1, transform: 'translateX(none)'}), animate('300ms', style({opacity: 0, transform: 'translateX(-10px)'})), ]), ]), ],})export class ReorderComponent { show = signal(true); items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock']; randomize() { const randItems = [...this.items]; const newItems = []; for (let i of this.items) { const max: number = this.items.length - newItems.length; const randNum = Math.floor(Math.random() * max); newItems.push(...randItems.splice(randNum, 1)); } this.items = newItems; }} <h1>Reordering List Example</h1><button type="button" (click)="randomize()">Randomize</button><ul class="items"> @for(item of items; track item) { <li @itemAnimation class="item">{{ item }}</li> }</ul> .items { list-style: none; padding: 0; margin: 0;} #### [With Native CSS](https://angular.dev/#with-native-css-8) import {Component, signal} from '@angular/core';@Component({ selector: 'app-reorder', templateUrl: './reorder.component.html', styleUrls: ['reorder.component.css'],})export class ReorderComponent { show = signal(true); items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock']; randomize() { const randItems = [...this.items]; const newItems = []; for (let i of this.items) { const max: number = this.items.length - newItems.length; const randNum = Math.floor(Math.random() * max); newItems.push(...randItems.splice(randNum, 1)); } this.items = newItems; }} <h1>Reordering List Example</h1><button type="button" (click)="randomize()">Randomize</button><ul class="items"> @for(item of items; track item) { <li class="item">{{ item }}</li> }</ul> .items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; @starting-style { opacity: 0; transform: translateX(-10px); }} ## [Migrating usages of AnimationPlayer](https://angular.dev/#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()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the `AnimationPlayer` 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](https://angular.dev/#route-transitions) You can use view transitions to animate between routes. See the [Route Transition Animations Guide](https://angular.dev/guide/animations/route-animations) to get started. --- ## Page: https://angular.dev/guide/animations/guide/animations/migration#transition-and-triggers ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/state ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/animations/transition-and-triggers#aliases-enter-and-leave ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/animations/complex-sequences ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/animateChild ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/query ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/animations ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/animations/reusable-animations ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/animations/route-animations ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/group ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/components/styling#style-scoping ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/animation ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/api/animations/useAnimation ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/guide/animations/guide/templates/binding#css-class-and-style-property-bindings ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/rxjs-interop **IMPORTANT:** The RxJS Interop package is available for [developer preview](https://angular.dev/reference/releases#developer-preview). It's ready for you to try, but it might change before it is stable. The `@angular/rxjs-interop` package offers APIs that help you integrate RxJS and Angular signals. ## [Create a signal from an RxJs Observable with `toSignal`](https://angular.dev/#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](https://angular.dev/#injection-context) `toSignal` by default needs to run in an [injection context](https://angular.dev/guide/di/dependency-injection-context), such as during construction of a component or service. If an injection context is not available, you can manually specify the `Injector` to use instead. ### [Initial values](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#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`](https://angular.dev/#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](https://angular.dev/#injection-context-1) `toObservable` by default needs to run in an [injection context](https://angular.dev/guide/di/dependency-injection-context), such as during construction of a component or service. If an injection context is not available, you can manually specify the `Injector` to use instead. ### [Timing of `toObservable`](https://angular.dev/#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. --- ## Page: https://angular.dev/ecosystem/rxjs-interop/output-interop The `@angular/rxjs-interop` package offers two APIs related to component and directive outputs. ## [Creating an output based on an RxJs Observable](https://angular.dev/#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](https://angular.dev/#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. --- ## Page: https://angular.dev/ecosystem/rxjs-interop/guide/components/outputs ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers Service workers augment the traditional web deployment model and empower applications to deliver a user experience with the reliability and performance on par with code that is written to run on your operating system and hardware. Adding a service worker to an Angular application is one of the steps for turning an application into a [Progressive Web App](https://web.dev/progressive-web-apps/) (also known as a PWA). At its simplest, a service worker is a script that runs in the web browser and manages caching for an application. Service workers function as a network proxy. They intercept all outgoing HTTP requests made by the application and can choose how to respond to them. For example, they can query a local cache and deliver a cached response if one is available. Proxying isn't limited to requests made through programmatic APIs, such as `fetch`; it also includes resources referenced in HTML and even the initial request to `index.html`. Service worker-based caching is thus completely programmable and doesn't rely on server-specified caching headers. Unlike the other scripts that make up an application, such as the Angular application bundle, the service worker is preserved after the user closes the tab. The next time that browser loads the application, the service worker loads first, and can intercept every request for resources to load the application. If the service worker is designed to do so, it can _completely satisfy the loading of the application, without the need for the network_. Even across a fast reliable network, round-trip delays can introduce significant latency when loading the application. Using a service worker to reduce dependency on the network can significantly improve the user experience. ## [Service workers in Angular](https://angular.dev/#service-workers-in-angular) Angular applications, as single-page applications, are in a prime position to benefit from the advantages of service workers. Angular ships with a service worker implementation. Angular developers can take advantage of this service worker and benefit from the increased reliability and performance it provides, without needing to code against low-level APIs. Angular's service worker is designed to optimize the end user experience of using an application over a slow or unreliable network connection, while also minimizing the risks of serving outdated content. To achieve this, the Angular service worker follows these guidelines: * Caching an application is like installing a native application. The application is cached as one unit, and all files update together. * A running application continues to run with the same version of all files. It does not suddenly start receiving cached files from a newer version, which are likely incompatible. * When users refresh the application, they see the latest fully cached version. New tabs load the latest cached code. * Updates happen in the background, relatively quickly after changes are published. The previous version of the application is served until an update is installed and ready. * The service worker conserves bandwidth when possible. Resources are only downloaded if they've changed. To support these behaviors, the Angular service worker loads a _manifest_ file from the server. The file, called `ngsw.json` (not to be confused with the [web app manifest](https://developer.mozilla.org/docs/Web/Manifest)), describes the resources to cache and includes hashes of every file's contents. When an update to the application is deployed, the contents of the manifest change, informing the service worker that a new version of the application should be downloaded and cached. This manifest is generated from a CLI-generated configuration file called `ngsw-config.json`. Installing the Angular service worker is as straightforward as [running an Angular CLI command](https://angular.dev/ecosystem/service-workers/getting-started#adding-a-service-worker-to-your-project). In addition to registering the Angular service worker with the browser, this also makes a few services available for injection which interact with the service worker and can be used to control it. For example, an application can ask to be notified when a new update becomes available, or an application can ask the service worker to check the server for available updates. ## [Before you start](https://angular.dev/#before-you-start) To make use of all the features of Angular service workers, use the latest versions of Angular and the [Angular CLI](https://angular.dev/tools/cli). For service workers to be registered, the application must be accessed over HTTPS, not HTTP. Browsers ignore service workers on pages that are served over an insecure connection. The reason is that service workers are quite powerful, so extra care is needed to ensure the service worker script has not been tampered with. There is one exception to this rule: to make local development more straightforward, browsers do _not_ require a secure connection when accessing an application on `localhost`. ### [Browser support](https://angular.dev/#browser-support) To benefit from the Angular service worker, your application must run in a web browser that supports service workers in general. Currently, service workers are supported in the latest versions of Chrome, Firefox, Edge, Safari, Opera, UC Browser (Android version) and Samsung Internet. Browsers like IE and Opera Mini do not support service workers. If the user is accessing your application with a browser that does not support service workers, the service worker is not registered and related behavior such as offline cache management and push notifications does not happen. More specifically: * The browser does not download the service worker script and the `ngsw.json` manifest file * Active attempts to interact with the service worker, such as calling `SwUpdate.checkForUpdate()`, return rejected promises * The observable events of related services, such as `SwUpdate.available`, are not triggered It is highly recommended that you ensure that your application works even without service worker support in the browser. Although an unsupported browser ignores service worker caching, it still reports errors if the application attempts to interact with the service worker. For example, calling `SwUpdate.checkForUpdate()` returns rejected promises. To avoid such an error, check whether the Angular service worker is enabled using `SwUpdate.isEnabled`. To learn more about other browsers that are service worker ready, see the [Can I Use](https://caniuse.com/#feat=serviceworkers) page and [MDN docs](https://developer.mozilla.org/docs/Web/API/Service_Worker_API). The rest of the articles in this section specifically address the Angular implementation of service workers. [Configuration file](https://angular.dev/ecosystem/service-workers/config) [Communicating with the Service Worker](https://angular.dev/ecosystem/service-workers/communications) [Push notifications](https://angular.dev/ecosystem/service-workers/push-notifications) [Service Worker devops](https://angular.dev/ecosystem/service-workers/devops) [App shell pattern](https://angular.dev/ecosystem/service-workers/app-shell) For more information about service workers in general, see [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers). For more information about browser support, see the [browser support](https://developers.google.com/web/fundamentals/primers/service-workers/#browser_support) section of [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers), Jake Archibald's [Is Serviceworker ready?](https://jakearchibald.github.io/isserviceworkerready), and [Can I Use](https://caniuse.com/serviceworkers). For additional recommendations and examples, see: [Precaching with Angular Service Worker](https://web.dev/precaching-with-the-angular-service-worker) [Creating a PWA with Angular CLI](https://web.dev/creating-pwa-with-angular-cli) ## [Next step](https://angular.dev/#next-step) To begin using Angular service workers, see [Getting Started with service workers](https://angular.dev/ecosystem/service-workers/getting-started). --- ## Page: https://angular.dev/ecosystem/service-workers/getting-started This document explains how to enable Angular service worker support in projects that you created with the [Angular CLI](https://angular.dev/tools/cli). It then uses an example to show you a service worker in action, demonstrating loading and basic caching. ## [Adding a service worker to your project](https://angular.dev/#adding-a-service-worker-to-your-project) To set up the Angular service worker in your project, run the following CLI command: ng add @angular/pwa The CLI configures your application to use service workers with the following actions: 1. Adds the `@angular/service-worker` package to your project. 2. Enables service worker build support in the CLI. 3. Imports and registers the service worker with the application's root providers. 4. Updates the `index.html` file: * Includes a link to add the `manifest.webmanifest` file * Adds a meta tag for `theme-color` 5. Installs icon files to support the installed Progressive Web App (PWA). 6. Creates the service worker configuration file called [`ngsw-config.json`](https://angular.dev/ecosystem/service-workers/config), which specifies the caching behaviors and other settings. Now, build the project: ng build The CLI project is now set up to use the Angular service worker. ## [Service worker in action: a tour](https://angular.dev/#service-worker-in-action-a-tour) This section demonstrates a service worker in action, using an example application. To enable service worker support during local development, use the production configuration with the following command: ng serve --configuration=production Alternatively, you can use the [`http-server package`](https://www.npmjs.com/package/http-server) from npm, which supports service worker applications. Run it without installation using: npx http-server -p 8080 -c-1 dist/<project-name>/browser This will serve your application with service worker support at [http://localhost:8080](http://localhost:8080/). ### [Initial load](https://angular.dev/#initial-load) With the server running on port `8080`, point your browser at `http://localhost:8080`. Your application should load normally. **TIP:** When testing Angular service workers, it's a good idea to use an incognito or private window in your browser to ensure the service worker doesn't end up reading from a previous leftover state, which can cause unexpected behavior. **HELPFUL:** If you are not using HTTPS, the service worker will only be registered when accessing the application on `localhost`. ### [Simulating a network issue](https://angular.dev/#simulating-a-network-issue) To simulate a network issue, disable network interaction for your application. In Chrome: 1. Select **Tools** > **Developer Tools** (from the Chrome menu located in the top right corner). 2. Go to the **Network tab**. 3. Select **Offline** in the **Throttling** dropdown menu.  Now the application has no access to network interaction. For applications that do not use the Angular service worker, refreshing now would display Chrome's Internet disconnected page that says "There is no Internet connection". With the addition of an Angular service worker, the application behavior changes. On a refresh, the page loads normally. Look at the Network tab to verify that the service worker is active.  **HELPFUL:** Under the "Size" column, the requests state is `(ServiceWorker)`. This means that the resources are not being loaded from the network. Instead, they are being loaded from the service worker's cache. ### [What's being cached?](https://angular.dev/#whats-being-cached) Notice that all of the files the browser needs to render this application are cached. The `ngsw-config.json` boilerplate configuration is set up to cache the specific resources used by the CLI: * `index.html` * `favicon.ico` * Build artifacts (JS and CSS bundles) * Anything under `assets` * Images and fonts directly under the configured `outputPath` (by default `./dist/<project-name>/`) or `resourcesOutputPath`. See the documentation for `ng build` for more information about these options. **IMPORTANT:** The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extensions. In some cases, you might want to modify the glob pattern to suit your needs. **IMPORTANT:** If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`. ### [Making changes to your application](https://angular.dev/#making-changes-to-your-application) Now that you've seen how service workers cache your application, the next step is understanding how updates work. Make a change to the application, and watch the service worker install the update: 1. If you're testing in an incognito window, open a second blank tab. This keeps the incognito and the cache state alive during your test. 2. Close the application tab, but not the window. This should also close the Developer Tools. 3. Shut down `http-server` (Ctrl-c). 4. Open `src/app/app.component.html` for editing. 5. Change the text `Welcome to {{title}}!` to `Bienvenue à {{title}}!`. 6. Build and run the server again: ng build npx http-server -p 8080 -c-1 dist/<project-name>/browser ### [Updating your application in the browser](https://angular.dev/#updating-your-application-in-the-browser) Now look at how the browser and service worker handle the updated application. 1. Open [http://localhost:8080](http://localhost:8080/) again in the same window. What happens?  What went wrong? _Nothing, actually!_ The Angular service worker is doing its job and serving the version of the application that it has **installed**, even though there is an update available. In the interest of speed, the service worker doesn't wait to check for updates before it serves the application that it has cached. Look at the `http-server` logs to see the service worker requesting `/ngsw.json`. [2023-09-07T00:37:24.372Z] "GET /ngsw.json?ngsw-cache-bust=0.9365263935102124" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36" This is how the service worker checks for updates. 2. Refresh the page.  The service worker installed the updated version of your application _in the background_, and the next time the page is loaded or reloaded, the service worker switches to the latest version. ## [More on Angular service workers](https://angular.dev/#more-on-angular-service-workers) You might also be interested in the following: [Configuration file](https://angular.dev/ecosystem/service-workers/config) [Communicating with the Service Worker](https://angular.dev/ecosystem/service-workers/communications) [Push notifications](https://angular.dev/ecosystem/service-workers/push-notifications) [Service Worker devops](https://angular.dev/ecosystem/service-workers/devops) [App shell pattern](https://angular.dev/ecosystem/service-workers/app-shell) --- ## Page: https://angular.dev/ecosystem/service-workers/config This topic describes the properties of the service worker configuration file. ## [Modifying the configuration](https://angular.dev/#modifying-the-configuration) The `ngsw-config.json` JSON configuration file specifies which files and data URLs the Angular service worker should cache and how it should update the cached files and data. The [Angular CLI](https://angular.dev/tools/cli) processes this configuration file during `ng build`. All file paths must begin with `/`, which corresponds to the deployment directory — usually `dist/<project-name>` in CLI projects. Unless otherwise commented, patterns use a **limited**\* glob format that internally will be converted into regex: | Glob formats | Details | | --- | --- | | `**` | Matches 0 or more path segments | | `*` | Matches 0 or more characters excluding `/` | | `?` | Matches exactly one character excluding `/` | | `!` prefix | Marks the pattern as being negative, meaning that only files that don't match the pattern are included | ### Special characters need to be escaped Pay attention that some characters with a special meaning in a regular expression are not escaped and also the pattern is not wrapped in `^`/`$` in the internal glob to regex conversion. `$` is a special character in regex that matches the end of the string and will not be automatically escaped when converting the glob pattern to a regular expression. If you want to literally match the `$` character, you have to escape it yourself (with `\\$`). For example, the glob pattern `/foo/bar/$value` results in an unmatchable expression, because it is impossible to have a string that has any characters after it has ended. The pattern will not be automatically wrapped in `^` and `$` when converting it to a regular expression. Therefore, the patterns will partially match the request URLs. If you want your patterns to match the beginning and/or end of URLs, you can add `^`/`$` yourself. For example, the glob pattern `/foo/bar/*.js` will match both `.js` and `.json` files. If you want to only match `.js` files, use `/foo/bar/*.js$`. Example patterns: | Patterns | Details | | --- | --- | | `/**/*.html` | Specifies all HTML files | | `/*.html` | Specifies only HTML files in the root | | `!/**/*.map` | Exclude all sourcemaps | ## [Service worker configuration properties](https://angular.dev/#service-worker-configuration-properties) The following sections describe each property of the configuration file. ### [`appData`](https://angular.dev/#appdata) This section enables you to pass any data you want that describes this particular version of the application. The `SwUpdate` service includes that data in the update notifications. Many applications use this section to provide additional information for the display of UI popups, notifying users of the available update. ### [`index`](https://angular.dev/#index) Specifies the file that serves as the index page to satisfy navigation requests. Usually this is `/index.html`. ### [`assetGroups`](https://angular.dev/#assetgroups) _Assets_ are resources that are part of the application version that update along with the application. They can include resources loaded from the page's origin as well as third-party resources loaded from CDNs and other external URLs. As not all such external URLs might be known at build time, URL patterns can be matched. **HELPFUL:** For the service worker to handle resources that are loaded from different origins, make sure that [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) is correctly configured on each origin's server. This field contains an array of asset groups, each of which defines a set of asset resources and the policy by which they are cached. { "assetGroups": [ { … }, { … } ]} **HELPFUL:** When the ServiceWorker handles a request, it checks asset groups in the order in which they appear in `ngsw-config.json`. The first asset group that matches the requested resource handles the request. It is recommended that you put the more specific asset groups higher in the list. For example, an asset group that matches `/foo.js` should appear before one that matches `*.js`. Each asset group specifies both a group of resources and a policy that governs them. This policy determines when the resources are fetched and what happens when changes are detected. Asset groups follow the Typescript interface shown here: interface AssetGroup { name: string; installMode?: 'prefetch' | 'lazy'; updateMode?: 'prefetch' | 'lazy'; resources: { files?: string[]; urls?: string[]; }; cacheQueryOptions?: { ignoreSearch?: boolean; };} Each `AssetGroup` is defined by the following asset group properties. #### [`name`](https://angular.dev/#name) A `name` is mandatory. It identifies this particular group of assets between versions of the configuration. #### [`installMode`](https://angular.dev/#installmode) The `installMode` determines how these resources are initially cached. The `installMode` can be either of two values: | Values | Details | | --- | --- | | `prefetch` | Tells the Angular service worker to fetch every single listed resource while it's caching the current version of the application. This is bandwidth-intensive but ensures resources are available whenever they're requested, even if the browser is currently offline. | | `lazy` | Does not cache any of the resources up front. Instead, the Angular service worker only caches resources for which it receives requests. This is an on-demand caching mode. Resources that are never requested are not cached. This is useful for things like images at different resolutions, so the service worker only caches the correct assets for the particular screen and orientation. | Defaults to `prefetch`. #### [`updateMode`](https://angular.dev/#updatemode) For resources already in the cache, the `updateMode` determines the caching behavior when a new version of the application is discovered. Any resources in the group that have changed since the previous version are updated in accordance with `updateMode`. | Values | Details | | --- | --- | | `prefetch` | Tells the service worker to download and cache the changed resources immediately. | | `lazy` | Tells the service worker to not cache those resources. Instead, it treats them as unrequested and waits until they're requested again before updating them. An `updateMode` of `lazy` is only valid if the `installMode` is also `lazy`. | Defaults to the value `installMode` is set to. #### [`resources`](https://angular.dev/#resources) This section describes the resources to cache, broken up into the following groups: | Resource groups | Details | | --- | --- | | `files` | Lists patterns that match files in the distribution directory. These can be single files or glob-like patterns that match a number of files. | | `urls` | Includes both URLs and URL patterns that are matched at runtime. These resources are not fetched directly and do not have content hashes, but they are cached according to their HTTP headers. This is most useful for CDNs such as the Google Fonts service. _(Negative glob patterns are not supported and `?` will be matched literally; that is, it will not match any character other than `?`.)_ | #### [`cacheQueryOptions`](https://angular.dev/#cachequeryoptions) These options are used to modify the matching behavior of requests. They are passed to the browsers `Cache#match` function. See [MDN](https://developer.mozilla.org/docs/Web/API/Cache/match) for details. Currently, only the following options are supported: | Options | Details | | --- | --- | | `ignoreSearch` | Ignore query parameters. Defaults to `false`. | ### [`dataGroups`](https://angular.dev/#datagroups) Unlike asset resources, data requests are not versioned along with the application. They're cached according to manually-configured policies that are more useful for situations such as API requests and other data dependencies. This field contains an array of data groups, each of which defines a set of data resources and the policy by which they are cached. { "dataGroups": [ { … }, { … } ]} **HELPFUL:** When the ServiceWorker handles a request, it checks data groups in the order in which they appear in `ngsw-config.json`. The first data group that matches the requested resource handles the request. It is recommended that you put the more specific data groups higher in the list. For example, a data group that matches `/api/foo.json` should appear before one that matches `/api/*.json`. Data groups follow this Typescript interface: export interface DataGroup { name: string; urls: string[]; version?: number; cacheConfig: { maxSize: number; maxAge: string; timeout?: string; refreshAhead?: string; strategy?: 'freshness' | 'performance'; }; cacheQueryOptions?: { ignoreSearch?: boolean; };} Each `DataGroup` is defined by the following data group properties. #### [`name`](https://angular.dev/#name-1) Similar to `assetGroups`, every data group has a `name` which uniquely identifies it. #### [`urls`](https://angular.dev/#urls) A list of URL patterns. URLs that match these patterns are cached according to this data group's policy. Only non-mutating requests (GET and HEAD) are cached. * Negative glob patterns are not supported * `?` is matched literally; that is, it matches _only_ the character `?` #### [`version`](https://angular.dev/#version) Occasionally APIs change formats in a way that is not backward-compatible. A new version of the application might not be compatible with the old API format and thus might not be compatible with existing cached resources from that API. `version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries —those from previous versions— should be discarded. `version` is an integer field and defaults to `1`. #### [`cacheConfig`](https://angular.dev/#cacheconfig) The following properties define the policy by which matching requests are cached. ##### [`maxSize`](https://angular.dev/#maxsize) The maximum number of entries, or responses, in the cache. **CRITICAL:** Open-ended caches can grow in unbounded ways and eventually exceed storage quotas, resulting in eviction. ##### [`maxAge`](https://angular.dev/#maxage) The `maxAge` parameter indicates how long responses are allowed to remain in the cache before being considered invalid and evicted. `maxAge` is a duration string, using the following unit suffixes: | Suffixes | Details | | --- | --- | | `d` | Days | | `h` | Hours | | `m` | Minutes | | `s` | Seconds | | `u` | Milliseconds | For example, the string `3d12h` caches content for up to three and a half days. ##### [`timeout`](https://angular.dev/#timeout) This duration string specifies the network timeout. The network timeout is how long the Angular service worker waits for the network to respond before using a cached response, if configured to do so. `timeout` is a duration string, using the following unit suffixes: | Suffixes | Details | | --- | --- | | `d` | Days | | `h` | Hours | | `m` | Minutes | | `s` | Seconds | | `u` | Milliseconds | For example, the string `5s30u` translates to five seconds and 30 milliseconds of network timeout. ##### [`refreshAhead`](https://angular.dev/#refreshahead) This duration string specifies the time ahead of the expiration of a cached resource when the Angular service worker should proactively attempt to refresh the resource from the network. The `refreshAhead` duration is an optional configuration that determines how much time before the expiration of a cached response the service worker should initiate a request to refresh the resource from the network. | Suffixes | Details | | --- | --- | | `d` | Days | | `h` | Hours | | `m` | Minutes | | `s` | Seconds | | `u` | Milliseconds | For example, the string `1h30m` translates to one hour and 30 minutes ahead of the expiration time. ##### [`strategy`](https://angular.dev/#strategy) The Angular service worker can use either of two caching strategies for data resources. | Caching strategies | Details | | --- | --- | | `performance` | The default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used, and no network request is made. This allows for some staleness, depending on the `maxAge`, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images. | | `freshness` | Optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to `timeout`, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances. | **HELPFUL:** You can also emulate a third strategy, [staleWhileRevalidate](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate), which returns cached data if it is available, but also fetches fresh data from the network in the background for next time. To use this strategy set `strategy` to `freshness` and `timeout` to `0u` in `cacheConfig`. This essentially does the following: 1. Try to fetch from the network first. 2. If the network request does not complete immediately, that is after a timeout of 0 ms, ignore the cache age and fall back to the cached value. 3. Once the network request completes, update the cache for future requests. 4. If the resource does not exist in the cache, wait for the network request anyway. ##### [`cacheOpaqueResponses`](https://angular.dev/#cacheopaqueresponses) Whether the Angular service worker should cache opaque responses or not. If not specified, the default value depends on the data group's configured strategy: | Strategies | Details | | --- | --- | | Groups with the `freshness` strategy | The default value is `true` and the service worker caches opaque responses. These groups will request the data every time and only fall back to the cached response when offline or on a slow network. Therefore, it doesn't matter if the service worker caches an error response. | | Groups with the `performance` strategy | The default value is `false` and the service worker doesn't cache opaque responses. These groups would continue to return a cached response until `maxAge` expires, even if the error was due to a temporary network or server issue. Therefore, it would be problematic for the service worker to cache an error response. | ### Comment on opaque responses In case you are not familiar, an [opaque response](https://fetch.spec.whatwg.org/#concept-filtered-response-opaque) is a special type of response returned when requesting a resource that is on a different origin which doesn't return CORS headers. One of the characteristics of an opaque response is that the service worker is not allowed to read its status, meaning it can't check if the request was successful or not. See [Introduction to `fetch()`](https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types) for more details. If you are not able to implement CORS — for example, if you don't control the origin — prefer using the `freshness` strategy for resources that result in opaque responses. #### [`cacheQueryOptions`](https://angular.dev/#cachequeryoptions-1) See [assetGroups](https://angular.dev/#assetgroups) for details. ### [`navigationUrls`](https://angular.dev/#navigationurls) This optional section enables you to specify a custom list of URLs that will be redirected to the index file. #### [Handling navigation requests](https://angular.dev/#handling-navigation-requests) The ServiceWorker redirects navigation requests that don't match any `asset` or `data` group to the specified [index file](https://angular.dev/#index). A request is considered to be a navigation request if: * Its [method](https://developer.mozilla.org/docs/Web/API/Request/method) is `GET` * Its [mode](https://developer.mozilla.org/docs/Web/API/Request/mode) is `navigation` * It accepts a `text/html` response as determined by the value of the `Accept` header * Its URL matches the following criteria: * The URL must not contain a file extension (that is, a `.`) in the last path segment * The URL must not contain `__` **HELPFUL:** To configure whether navigation requests are sent through to the network or not, see the [navigationRequestStrategy](https://angular.dev/#navigationrequeststrategy) section and [applicationMaxAge](https://angular.dev/#application-max-age) sections. #### [Matching navigation request URLs](https://angular.dev/#matching-navigation-request-urls) While these default criteria are fine in most cases, it is sometimes desirable to configure different rules. For example, you might want to ignore specific routes, such as those that are not part of the Angular app, and pass them through to the server. This field contains an array of URLs and [glob-like](https://angular.dev/#modifying-the-configuration) URL patterns that are matched at runtime. It can contain both negative patterns (that is, patterns starting with `!`) and non-negative patterns and URLs. Only requests whose URLs match _any_ of the non-negative URLs/patterns and _none_ of the negative ones are considered navigation requests. The URL query is ignored when matching. If the field is omitted, it defaults to: [ '/**', // Include all URLs. '!/**/*.*', // Exclude URLs to files (containing a file extension in the last segment). '!/**/*__*', // Exclude URLs containing `__` in the last segment. '!/**/*__*/**', // Exclude URLs containing `__` in any other segment.] ### [`navigationRequestStrategy`](https://angular.dev/#navigationrequeststrategy) This optional property enables you to configure how the service worker handles navigation requests: { "navigationRequestStrategy": "freshness"} | Possible values | Details | | --- | --- | | `'performance'` | The default setting. Serves the specified [index file](https://angular.dev/#index-file), which is typically cached. | | `'freshness'` | Passes the requests through to the network and falls back to the `performance` behavior when offline. This value is useful when the server redirects the navigation requests elsewhere using a `3xx` HTTP redirect status code. Reasons for using this value include: * Redirecting to an authentication website when authentication is not handled by the application * Redirecting specific URLs to avoid breaking existing links/bookmarks after a website redesign * Redirecting to a different website, such as a server-status page, while a page is temporarily down | **IMPORTANT:** The `freshness` strategy usually results in more requests sent to the server, which can increase response latency. It is recommended that you use the default performance strategy whenever possible. ### [`applicationMaxAge`](https://angular.dev/#applicationmaxage) This optional property enables you to configure how long the service worker will cache any requests. Within the `maxAge`, files will be served from cache. Beyond it, all requests will only be served from the network, including asset and data requests. --- ## Page: https://angular.dev/ecosystem/service-workers/communications Enabling service worker support does more than just register the service worker; it also provides services you can use to interact with the service worker and control the caching of your application. ## [`SwUpdate` service](https://angular.dev/#swupdate-service) The `SwUpdate` service gives you access to events that indicate when the service worker discovers and installs an available update for your application. The `SwUpdate` service supports three separate operations: * Receiving notifications when an updated version is _detected_ on the server, _installed and ready_ to be used locally or when an _installation fails_. * Asking the service worker to check the server for new updates. * Asking the service worker to activate the latest version of the application for the current tab. ### [Version updates](https://angular.dev/#version-updates) The `versionUpdates` is an `Observable` property of `SwUpdate` and emits four event types: | Event types | Details | | --- | --- | | `VersionDetectedEvent` | Emitted when the service worker has detected a new version of the app on the server and is about to start downloading it. | | `NoNewVersionDetectedEvent` | Emitted when the service worker has checked the version of the app on the server and did not find a new version. | | `VersionReadyEvent` | Emitted when a new version of the app is available to be activated by clients. It may be used to notify the user of an available update or prompt them to refresh the page. | | `VersionInstallationFailedEvent` | Emitted when the installation of a new version failed. It may be used for logging/monitoring purposes. | import {inject, Injectable} from '@angular/core';import {SwUpdate, VersionReadyEvent} from '@angular/service-worker';@Injectable({providedIn: 'root'})export class LogUpdateService { private updates = inject(SwUpdate); constructor() { this.updates.versionUpdates.subscribe((evt) => { switch (evt.type) { case 'VERSION_DETECTED': console.log(`Downloading new app version: ${evt.version.hash}`); break; case 'VERSION_READY': console.log(`Current app version: ${evt.currentVersion.hash}`); console.log(`New app version ready for use: ${evt.latestVersion.hash}`); break; case 'VERSION_INSTALLATION_FAILED': console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`); break; } }); }} ### [Checking for updates](https://angular.dev/#checking-for-updates) It's possible to ask the service worker to check if any updates have been deployed to the server. The service worker checks for updates during initialization and on each navigation request —that is, when the user navigates from a different address to your application. However, you might choose to manually check for updates if you have a site that changes frequently or want updates to happen on a schedule. Do this with the `checkForUpdate()` method: import {ApplicationRef, inject, Injectable} from '@angular/core';import {SwUpdate} from '@angular/service-worker';import {concat, interval} from 'rxjs';import {first} from 'rxjs/operators';@Injectable({providedIn: 'root'})export class CheckForUpdateService { private appRef = inject(ApplicationRef); private updates = inject(SwUpdate); constructor() { // Allow the app to stabilize first, before starting // polling for updates with `interval()`. const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true)); const everySixHours$ = interval(6 * 60 * 60 * 1000); const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$); everySixHoursOnceAppIsStable$.subscribe(async () => { try { const updateFound = await this.updates.checkForUpdate(); console.log(updateFound ? 'A new version is available.' : 'Already on the latest version.'); } catch (err) { console.error('Failed to check for updates:', err); } }); }} This method returns a `Promise<boolean>` which indicates if an update is available for activation. The check might fail, which will cause a rejection of the `Promise`. ### Stabilization and service worker registration In order to avoid negatively affecting the initial rendering of the page, by default the Angular service worker service waits for up to 30 seconds for the application to stabilize before registering the ServiceWorker script. Constantly polling for updates, for example, with [setInterval()](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) or RxJS' [interval()](https://rxjs.dev/api/index/function/interval), prevents the application from stabilizing and the ServiceWorker script is not registered with the browser until the 30 seconds upper limit is reached. This is true for any kind of polling done by your application. Check the [isStable](https://angular.dev/api/core/ApplicationRef#isStable) documentation for more information. Avoid that delay by waiting for the application to stabilize first, before starting to poll for updates, as shown in the preceding example. Alternatively, you might want to define a different [registration strategy](https://angular.dev/api/service-worker/SwRegistrationOptions#registrationStrategy) for the ServiceWorker. ### [Updating to the latest version](https://angular.dev/#updating-to-the-latest-version) You can update an existing tab to the latest version by reloading the page as soon as a new version is ready. To avoid disrupting the user's progress, it is generally a good idea to prompt the user and let them confirm that it is OK to reload the page and update to the latest version: import {inject, Injectable} from '@angular/core';import {filter, map} from 'rxjs/operators';import {SwUpdate, VersionReadyEvent} from '@angular/service-worker';function promptUser(event: VersionReadyEvent): boolean { return true;}@Injectable({providedIn: 'root'})export class PromptUpdateService { constructor() { const swUpdate = inject(SwUpdate); swUpdate.versionUpdates .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY')) .subscribe((evt) => { if (promptUser(evt)) { // Reload the page to update to the latest version. document.location.reload(); } }); // ... const updatesAvailable = swUpdate.versionUpdates.pipe( filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'), map((evt) => ({ type: 'UPDATE_AVAILABLE', current: evt.currentVersion, available: evt.latestVersion, })), ); }} ### Safety of updating without reloading Calling `activateUpdate()` updates a tab to the latest version without reloading the page, but this could break the application. Updating without reloading can create a version mismatch between the application shell and other page resources, such as lazy-loaded chunks, whose filenames may change between versions. You should only use `activateUpdate()`, if you are certain it is safe for your specific use case. ### [Handling an unrecoverable state](https://angular.dev/#handling-an-unrecoverable-state) In some cases, the version of the application used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload. For example, imagine the following scenario: 1. A user opens the application for the first time and the service worker caches the latest version of the application. Assume the application's cached assets include `index.html`, `main.<main-hash-1>.js` and `lazy-chunk.<lazy-hash-1>.js`. 2. The user closes the application and does not open it for a while. 3. After some time, a new version of the application is deployed to the server. This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`. **IMPORTANT:** The hashes are different now, because the content of the files changed. The old version is no longer available on the server. 1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache. Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space. 2. The user opens the application again. The service worker serves the latest version known to it at this point, namely the old version (`index.html` and `main.<main-hash-1>.js`). 3. At some later point, the application requests the lazy bundle, `lazy-chunk.<lazy-hash-1>.js`. 4. The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (because the server now only has `lazy-chunk.<lazy-hash-2>.js` from the newer version). In the preceding scenario, the service worker is not able to serve an asset that would normally be cached. That particular application version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an `UnrecoverableStateEvent` event. Subscribe to `SwUpdate#unrecoverable` to be notified and handle these errors. import {inject, Injectable} from '@angular/core';import {SwUpdate} from '@angular/service-worker';function notifyUser(message: string): void {}@Injectable({providedIn: 'root'})export class HandleUnrecoverableStateService { private updates = inject(SwUpdate); constructor() { this.updates.unrecoverable.subscribe((event) => { notifyUser( 'An error occurred that we cannot recover from:\n' + event.reason + '\n\nPlease reload the page.', ); }); }} ## [More on Angular service workers](https://angular.dev/#more-on-angular-service-workers) You might also be interested in the following: [Push notifications](https://angular.dev/ecosystem/service-workers/push-notifications) [Service Worker devops](https://angular.dev/ecosystem/service-workers/devops) --- ## Page: https://angular.dev/ecosystem/service-workers/push-notifications Push notifications are a compelling way to engage users. Through the power of service workers, notifications can be delivered to a device even when your application is not in focus. The Angular service worker enables the display of push notifications and the handling of notification click events. **HELPFUL:** When using the Angular service worker, push notification interactions are handled using the `SwPush` service. To learn more about the browser APIs involved see [Push API](https://developer.mozilla.org/docs/Web/API/Push_API) and [Using the Notifications API](https://developer.mozilla.org/docs/Web/API/Notifications_API/Using_the_Notifications_API). ## [Notification payload](https://angular.dev/#notification-payload) Invoke push notifications by pushing a message with a valid payload. See `SwPush` for guidance. **HELPFUL:** In Chrome, you can test push notifications without a backend. Open Devtools -> Application -> Service Workers and use the `Push` input to send a JSON notification payload. ## [Notification click handling](https://angular.dev/#notification-click-handling) The default behavior for the `notificationclick` event is to close the notification and notify `SwPush.notificationClicks`. You can specify an additional operation to be executed on `notificationclick` by adding an `onActionClick` property to the `data` object, and providing a `default` entry. This is especially useful for when there are no open clients when a notification is clicked. { "notification": { "title": "New Notification!", "data": { "onActionClick": { "default": {"operation": "openWindow", "url": "foo"} } } }} ### [Operations](https://angular.dev/#operations) The Angular service worker supports the following operations: | Operations | Details | | --- | --- | | `openWindow` | Opens a new tab at the specified URL. | | `focusLastFocusedOrOpen` | Focuses the last focused client. If there is no client open, then it opens a new tab at the specified URL. | | `navigateLastFocusedOrOpen` | Focuses the last focused client and navigates it to the specified URL. If there is no client open, then it opens a new tab at the specified URL. | | `sendRequest` | Send a simple GET request to the specified URL. | **IMPORTANT:** URLs are resolved relative to the service worker's registration scope. If an `onActionClick` item does not define a `url`, then the service worker's registration scope is used. ### [Actions](https://angular.dev/#actions) Actions offer a way to customize how the user can interact with a notification. Using the `actions` property, you can define a set of available actions. Each action is represented as an action button that the user can click to interact with the notification. In addition, using the `onActionClick` property on the `data` object, you can tie each action to an operation to be performed when the corresponding action button is clicked: { "notification": { "title": "New Notification!", "actions": [ {"action": "foo", "title": "Open new tab"}, {"action": "bar", "title": "Focus last"}, {"action": "baz", "title": "Navigate last"}, {"action": "qux", "title": "Send request in the background"}, {"action": "other", "title": "Just notify existing clients"} ], "data": { "onActionClick": { "default": {"operation": "openWindow"}, "foo": {"operation": "openWindow", "url": "/absolute/path"}, "bar": {"operation": "focusLastFocusedOrOpen", "url": "relative/path"}, "baz": {"operation": "navigateLastFocusedOrOpen", "url": "https://other.domain.com/"}, "qux": {"operation": "sendRequest", "url": "https://yet.another.domain.com/"} } } }} **IMPORTANT:** If an action does not have a corresponding `onActionClick` entry, then the notification is closed and `SwPush.notificationClicks` is notified on existing clients. ## [More on Angular service workers](https://angular.dev/#more-on-angular-service-workers) You might also be interested in the following: [Communicating with the Service Worker](https://angular.dev/ecosystem/service-workers/communications) [Service Worker devops](https://angular.dev/ecosystem/service-workers/devops) --- ## Page: https://angular.dev/ecosystem/service-workers/devops This page is a reference for deploying and supporting production applications that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available resources and fail-safes. ## [Service worker and caching of application resources](https://angular.dev/#service-worker-and-caching-of-application-resources) Imagine the Angular service worker as a forward cache or a Content Delivery Network (CDN) edge that is installed in the end user's web browser. The service worker responds to requests made by the Angular application for resources or data from a local cache, without needing to wait for the network. Like any cache, it has rules for how content is expired and updated. ### [Application versions](https://angular.dev/#application-versions) In the context of an Angular service worker, a "version" is a collection of resources that represent a specific build of the Angular application. Whenever a new build of the application is deployed, the service worker treats that build as a new version of the application. This is true even if only a single file is updated. At any given time, the service worker might have multiple versions of the application in its cache and it might be serving them simultaneously. For more information, see the [Application tabs](https://angular.dev/#application-tabs) section. To preserve application integrity, the Angular service worker groups all files into a version together. The files grouped into a version usually include HTML, JS, and CSS files. Grouping of these files is essential for integrity because HTML, JS, and CSS files frequently refer to each other and depend on specific content. For example, an `index.html` file might have a `<script>` tag that references `bundle.js` and it might attempt to call a function `startApp()` from within that script. Any time this version of `index.html` is served, the corresponding `bundle.js` must be served with it. For example, assume that the `startApp()` function is renamed to `runApp()` in both files. In this scenario, it is not valid to serve the old `index.html`, which calls `startApp()`, along with the new bundle, which defines `runApp()`. This file integrity is especially important when lazy loading. A JS bundle might reference many lazy chunks, and the filenames of the lazy chunks are unique to the particular build of the application. If a running application at version `X` attempts to load a lazy chunk, but the server has already updated to version `X + 1`, the lazy loading operation fails. The version identifier of the application is determined by the contents of all resources, and it changes if any of them change. In practice, the version is determined by the contents of the `ngsw.json` file, which includes hashes for all known content. If any of the cached files change, the file's hash changes in `ngsw.json`. This change causes the Angular service worker to treat the active set of files as a new version. **HELPFUL:** The build process creates the manifest file, `ngsw.json`, using information from `ngsw-config.json`. With the versioning behavior of the Angular service worker, an application server can ensure that the Angular application always has a consistent set of files. #### [Update checks](https://angular.dev/#update-checks) Every time the user opens or refreshes the application, the Angular service worker checks for updates to the application by looking for updates to the `ngsw.json` manifest. If an update is found, it is downloaded and cached automatically, and is served the next time the application is loaded. ### [Resource integrity](https://angular.dev/#resource-integrity) One of the potential side effects of long caching is inadvertently caching a resource that's not valid. In a normal HTTP cache, a hard refresh or the cache expiring limits the negative effects of caching a file that's not valid. A service worker ignores such constraints and effectively long-caches the entire application. It's important that the service worker gets the correct content, so it keeps hashes of the resources to maintain their integrity. #### [Hashed content](https://angular.dev/#hashed-content) To ensure resource integrity, the Angular service worker validates the hashes of all resources for which it has a hash. For an application created with the [Angular CLI](https://angular.dev/tools/cli), this is everything in the `dist` directory covered by the user's `src/ngsw-config.json` configuration. If a particular file fails validation, the Angular service worker attempts to re-fetch the content using a "cache-busting" URL parameter to prevent browser or intermediate caching. If that content also fails validation, the service worker considers the entire version of the application to not be valid and stops serving the application. If necessary, the service worker enters a safe mode where requests fall back on the network. The service worker doesn't use its cache if there's a high risk of serving content that is broken, outdated, or not valid. Hash mismatches can occur for a variety of reasons: * Caching layers between the origin server and the end user could serve stale content * A non-atomic deployment could result in the Angular service worker having visibility of partially updated content * Errors during the build process could result in updated resources without `ngsw.json` being updated. The reverse could also happen resulting in an updated `ngsw.json` without updated resources. #### [Unhashed content](https://angular.dev/#unhashed-content) The only resources that have hashes in the `ngsw.json` manifest are resources that were present in the `dist` directory at the time the manifest was built. Other resources, especially those loaded from CDNs, have content that is unknown at build time or are updated more frequently than the application is deployed. If the Angular service worker does not have a hash to verify a resource is valid, it still caches its contents. At the same time, it honors the HTTP caching headers by using a policy of _stale while revalidate_. The Angular service worker continues to serve a resource even after its HTTP caching headers indicate that it is no longer valid. At the same time, it attempts to refresh the expired resource in the background. This way, broken unhashed resources do not remain in the cache beyond their configured lifetimes. ### [Application tabs](https://angular.dev/#application-tabs) It can be problematic for an application if the version of resources it's receiving changes suddenly or without warning. See the [Application versions](https://angular.dev/#application-versions) section for a description of such issues. The Angular service worker provides a guarantee: a running application continues to run the same version of the application. If another instance of the application is opened in a new web browser tab, then the most current version of the application is served. As a result, that new tab can be running a different version of the application than the original tab. **IMPORTANT:** This guarantee is **stronger** than that provided by the normal web deployment model. Without a service worker, there is no guarantee that lazily loaded code is from the same version as the application's initial code. The Angular service worker might change the version of a running application under error conditions such as: * The current version becomes non-valid due to a failed hash. * An unrelated error causes the service worker to enter safe mode and deactivates it temporarily. The Angular service worker cleans up application versions when no tab is using them. Other reasons the Angular service worker might change the version of a running application are normal events: * The page is reloaded/refreshed. * The page requests an update be immediately activated using the `SwUpdate` service. ### [Service worker updates](https://angular.dev/#service-worker-updates) The Angular service worker is a small script that runs in web browsers. From time to time, the service worker is updated with bug fixes and feature improvements. The Angular service worker is downloaded when the application is first opened and when the application is accessed after a period of inactivity. If the service worker changes, it's updated in the background. Most updates to the Angular service worker are transparent to the application. The old caches are still valid and content is still served normally. Occasionally, a bug fix or feature in the Angular service worker might require the invalidation of old caches. In this case, the service worker transparently refreshes the application from the network. ### [Bypassing the service worker](https://angular.dev/#bypassing-the-service-worker) In some cases, you might want to bypass the service worker entirely and let the browser handle the request. An example is when you rely on a feature that is currently not supported in service workers, such as [reporting progress on uploaded files](https://github.com/w3c/ServiceWorker/issues/1141). To bypass the service worker, set `ngsw-bypass` as a request header, or as a query parameter. The value of the header or query parameter is ignored and can be empty or omitted. ### [Service worker requests when the server can't be reached](https://angular.dev/#service-worker-requests-when-the-server-cant-be-reached) The service worker processes all requests unless the [service worker is explicitly bypassed](https://angular.dev/#bypassing-the-service-worker). The service worker either returns a cached response or sends the request to the server, depending on the state and configuration of the cache. The service worker only caches responses to non-mutating requests, such as `GET` and `HEAD`. If the service worker receives an error from the server or it doesn't receive a response, it returns an error status that indicates the result of the call. For example, if the service worker doesn't receive a response, it creates a [504 Gateway Timeout](https://developer.mozilla.org/docs/Web/HTTP/Status/504) status to return. The `504` status in this example could be returned because the server is offline or the client is disconnected. ## [Debugging the Angular service worker](https://angular.dev/#debugging-the-angular-service-worker) Occasionally, it might be necessary to examine the Angular service worker in a running state to investigate issues or whether it's operating as designed. Browsers provide built-in tools for debugging service workers and the Angular service worker itself includes useful debugging features. ### [Locating and analyzing debugging information](https://angular.dev/#locating-and-analyzing-debugging-information) The Angular service worker exposes debugging information under the `ngsw/` virtual directory. Currently, the single exposed URL is `ngsw/state`. Here is an example of this debug page's contents: NGSW Debug Info:Driver version: 13.3.7Driver state: NORMAL ((nominal))Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5cLast update check: never=== Version eea7f5f464f90789b621170af5a569d6be077e5c ===Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65=== Idle Task Queue ===Last update tick: 1s496uLast update run: neverTask queue: * init post-load (update, cleanup)Debug log: #### [Driver state](https://angular.dev/#driver-state) The first line indicates the driver state: Driver state: NORMAL ((nominal)) `NORMAL` indicates that the service worker is operating normally and is not in a degraded state. There are two possible degraded states: | Degraded states | Details | | --- | --- | | `EXISTING_CLIENTS_ONLY` | The service worker does not have a clean copy of the latest known version of the application. Older cached versions are safe to use, so existing tabs continue to run from cache, but new loads of the application will be served from the network. The service worker will try to recover from this state when a new version of the application is detected and installed. This happens when a new `ngsw.json` is available. | | `SAFE_MODE` | The service worker cannot guarantee the safety of using cached data. Either an unexpected error occurred or all cached versions are invalid. All traffic will be served from the network, running as little service worker code as possible. | In both cases, the parenthetical annotation provides the error that caused the service worker to enter the degraded state. Both states are temporary; they are saved only for the lifetime of the [ServiceWorker instance](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope). The browser sometimes terminates an idle service worker to conserve memory and processor power, and creates a new service worker instance in response to network events. The new instance starts in the `NORMAL` mode, regardless of the state of the previous instance. #### [Latest manifest hash](https://angular.dev/#latest-manifest-hash) Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c This is the SHA1 hash of the most up-to-date version of the application that the service worker knows about. #### [Last update check](https://angular.dev/#last-update-check) Last update check: never This indicates the last time the service worker checked for a new version, or update, of the application. `never` indicates that the service worker has never checked for an update. In this example debug file, the update check is currently scheduled, as explained the next section. #### [Version](https://angular.dev/#version) === Version eea7f5f464f90789b621170af5a569d6be077e5c ===Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65 In this example, the service worker has one version of the application cached and being used to serve two different tabs. **HELPFUL:** This version hash is the "latest manifest hash" listed above. Both clients are on the latest version. Each client is listed by its ID from the `Clients` API in the browser. #### [Idle task queue](https://angular.dev/#idle-task-queue) === Idle Task Queue ===Last update tick: 1s496uLast update run: neverTask queue: * init post-load (update, cleanup) The Idle Task Queue is the queue of all pending tasks that happen in the background in the service worker. If there are any tasks in the queue, they are listed with a description. In this example, the service worker has one such task scheduled, a post-initialization operation involving an update check and cleanup of stale caches. The last update tick/run counters give the time since specific events happened related to the idle queue. The "Last update run" counter shows the last time idle tasks were actually executed. "Last update tick" shows the time since the last event after which the queue might be processed. #### [Debug log](https://angular.dev/#debug-log) Debug log: Errors that occur within the service worker are logged here. ### [Developer tools](https://angular.dev/#developer-tools) Browsers such as Chrome provide developer tools for interacting with service workers. Such tools can be powerful when used properly, but there are a few things to keep in mind. * When using developer tools, the service worker is kept running in the background and never restarts. This can cause behavior with Dev Tools open to differ from behavior a user might experience. * If you look in the Cache Storage viewer, the cache is frequently out of date. Right-click the Cache Storage title and refresh the caches. * Stopping and starting the service worker in the Service Worker pane checks for updates ## [Service worker safety](https://angular.dev/#service-worker-safety) Bugs or broken configurations could cause the Angular service worker to act in unexpected ways. If this happens, the Angular service worker contains several failsafe mechanisms in case an administrator needs to deactivate the service worker quickly. ### [Fail-safe](https://angular.dev/#fail-safe) To deactivate the service worker, rename the `ngsw.json` file or delete it. When the service worker's request for `ngsw.json` returns a `404`, then the service worker removes all its caches and de-registers itself, essentially self-destructing. ### [Safety worker](https://angular.dev/#safety-worker) A small script, `safety-worker.js`, is also included in the `@angular/service-worker` NPM package. When loaded, it un-registers itself from the browser and removes the service worker caches. This script can be used as a last resort to get rid of unwanted service workers already installed on client pages. **CRITICAL:** You cannot register this worker directly, as old clients with cached state might not see a new `index.html` which installs the different worker script. Instead, you must serve the contents of `safety-worker.js` at the URL of the Service Worker script you are trying to unregister. You must continue to do so until you are certain all users have successfully unregistered the old worker. For most sites, this means that you should serve the safety worker at the old Service Worker URL forever. This script can be used to deactivate `@angular/service-worker` and remove the corresponding caches. It also removes any other Service Workers which might have been served in the past on your site. ### [Changing your application's location](https://angular.dev/#changing-your-applications-location) **IMPORTANT:** Service workers don't work behind redirect. You might have already encountered the error `The script resource is behind a redirect, which is disallowed`. This can be a problem if you have to change your application's location. If you set up a redirect from the old location, such as `example.com`, to the new location, `www.example.com` in this example, the worker stops working. Also, the redirect won't even trigger for users who are loading the site entirely from Service Worker. The old worker, which was registered at `example.com`, tries to update and sends a request to the old location `example.com`. This request is redirected to the new location `www.example.com` and creates the error: `The script resource is behind a redirect, which is disallowed`. To remedy this, you might need to deactivate the old worker using one of the preceding techniques: [Fail-safe](https://angular.dev/#fail-safe) or [Safety Worker](https://angular.dev/#safety-worker). ## [More on Angular service workers](https://angular.dev/#more-on-angular-service-workers) You might also be interested in the following: [Configuration file](https://angular.dev/ecosystem/service-workers/config) [Communicating with the Service Worker](https://angular.dev/ecosystem/service-workers/communications) --- ## Page: https://angular.dev/ecosystem/service-workers/app-shell The [App shell pattern](https://developer.chrome.com/blog/app-shell) is a way to render a portion of your application using a route at build time. It can improve the user experience by quickly launching a static rendered page (a skeleton common to all pages) while the browser downloads the full client version and switches to it automatically after the code loads. This gives users a meaningful first paint of your application that appears quickly because the browser can render the HTML and CSS without the need to initialize any JavaScript. 1. ### [Prepare the application](https://angular.dev/#prepare-the-application) Do this with the following Angular CLI command: ng new my-app --routing For an existing application, you have to manually add the `Router` and defining a `<router-outlet>` within your application. 2. ### [Create the application shell](https://angular.dev/#create-the-application-shell) Use the Angular CLI to automatically create the application shell. ng generate app-shell For more information about this command, see [App shell command](https://angular.dev/cli/generate/app-shell). The command updates the application code and adds extra files to the project structure. src├── app│ ├── app.config.server.ts # server application configuration│ └── app-shell # app-shell component│ ├── app-shell.component.html│ ├── app-shell.component.scss│ ├── app-shell.component.spec.ts│ └── app-shell.component.ts└── main.server.ts # main server application bootstrapping ng build --configuration=development Or to use the production configuration. ng build To verify the build output, open `dist/my-app/browser/index.html`. Look for default text `app-shell works!` to show that the application shell route was rendered as part of the output. --- ## Page: https://angular.dev/ecosystem/service-workers/tools/cli ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/ecosystem/service-workers/config ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/ecosystem/service-workers/communications ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/ecosystem/service-workers/push-notifications ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/ecosystem/service-workers/devops ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/ecosystem/service-workers/app-shell ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/api/core/ApplicationRef#isStable ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/api/service-worker/SwRegistrationOptions#registrationStrategy ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/service-workers/cli/generate/app-shell ## Works at any scale Angular lets you start small on a well-lit path and supports you as your team and apps grow. ## Loved by millions Join the millions of developers all over the world building with Angular in a thriving and friendly community. ## Build for everyone Rely on Angular's built-in hydration, internationalization, security, and accessibility support to build for everyone around the world. --- ## Page: https://angular.dev/ecosystem/web-workers [Web workers](https://developer.mozilla.org/docs/Web/API/Web_Workers_API) let you run CPU-intensive computations in a background thread, freeing the main thread to update the user interface. Application's performing a lot of computations, like generating Computer-Aided Design (CAD) drawings or doing heavy geometric calculations, can use web workers to increase performance. **HELPFUL:** The Angular CLI does not support running itself in a web worker. ## [Adding a web worker](https://angular.dev/#adding-a-web-worker) To add a web worker to an existing project, use the Angular CLI `ng generate` command. ng generate web-worker <location> You can add a web worker anywhere in your application. For example, to add a web worker to the root component, `src/app/app.component.ts`, run the following command. ng generate web-worker app The command performs the following actions. 1. Configures your project to use web workers, if it isn't already. 2. Adds the following scaffold code to `src/app/app.worker.ts` to receive messages. addEventListener('message', ({ data }) => { const response = `worker response to ${data}`; postMessage(response); }); 3. Adds the following scaffold code to `src/app/app.component.ts` to use the worker. if (typeof Worker !== 'undefined') { // Create a new const worker = new Worker(new URL('./app.worker', import.meta.url)); worker.onmessage = ({ data }) => { console.log(`page got message: ${data}`); }; worker.postMessage('hello'); } else { // Web workers are not supported in this environment. // You should add a fallback so that your program still executes correctly. } After you create this initial scaffold, you must refactor your code to use the web worker by sending messages to and from the worker. **IMPORTANT:** Some environments or platforms, such as `@angular/platform-server` used in [Server-side Rendering](https://angular.dev/guide/ssr), don't support web workers. To ensure that your application works in these environments, you must provide a fallback mechanism to perform the computations that the worker would otherwise perform. --- ## Page: https://angular.dev/ecosystem/custom-build-pipeline When building an Angular app we strongly recommend you to use the Angular CLI to leverage its structure-dependent update functionality and build system abstraction. This way your projects benefit from the latest security, performance, and API improvements and transparent build improvements. This page explores the **rare use cases** when you need a custom build pipeline that does not use the Angular CLI. All listed tools below are open source build plugins that are maintained by members of the Angular community. To learn more about their support model and maintenance status look at their documentation and GitHub repository URLs. ## [When should you use a custom build pipeline?](https://angular.dev/#when-should-you-use-a-custom-build-pipeline) There are some niche use cases when you may want to maintain a custom build pipeline. For example: * You have an existing app using a different toolchain and you’d like to add Angular to it * You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation) * You’d like to create an short-lived experiment using your favorite build tool ## [What are the options?](https://angular.dev/#what-are-the-options) Currently, there are two well supported community tools that enable you to create a custom build pipeline with a [Vite plugin](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) and [Rspack plugin](https://www.npmjs.com/package/@nx/angular-rspack). Both of them use underlying abstractions that power the Angular CLI. They allow you to create a flexible build pipeline and require manual maintenance and no automated update experience. ### [Rspack](https://angular.dev/#rspack) Rspack is a Rust-based bundler that aims to provide compatibility with the webpack plugin ecosystem. If your project is tightly coupled to the webpack ecosystem, heavily relying on a custom webpack configuration you can leverage Rspack to improve your build times. You can find more about Angular Rspack on the project’s [documentation website](https://nx.dev/recipes/angular/rspack/introduction). ### [Vite](https://angular.dev/#vite) Vite is a frontend build tool that aims to provide a faster and leaner development experience for modern web projects. Vite is also extensible through its plugin system that allows ecosystems to build integrations with Vite, such as Vitest for unit and browser testing, Storybook for authoring components in isolation, and more. The Angular CLI also uses Vite as its development server. The [AnalogJS Vite plugin for Angular](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) enables the adoption of Angular with a project or framework that uses or is built on top of Vite. This can consist of developing and building an Angular project with Vite directly, or adding Angular to an existing project or pipeline. One example is integrating Angular UI components into a documentation website using [Astro and Starlight](https://analogjs.org/docs/packages/astro-angular/overview). You can learn more about AnalogJS and how to use the plugin through its [documentation page](https://analogjs.org/docs/packages/vite-plugin-angular/overview). --- ## Page: https://github.com/angular/angularfire#readme ## AngularFire [](#angularfire) AngularFire smooths over the rough edges an Angular developer might encounter when implementing the framework-agnostic [Firebase JS SDK](https://github.com/firebase/firebase-js-sdk) & aims to provide a more natural developer experience by conforming to Angular conventions. **ng add @angular/fire** * **Dependency injection** - Provide and Inject Firebase services in your components. * **Zone.js wrappers** - Stable zones allow proper functionality of service workers, forms, SSR, and pre-rendering. * **Observable based** - Utilize RxJS rather than callbacks for real-time streams. * **NgRx friendly API** - Integrate with NgRx using AngularFire's action based APIs. * **Lazy-loading** - AngularFire dynamically imports much of Firebase, reducing the time to load your app. * **Deploy schematics** - Get your Angular application deployed on Firebase Hosting with a single command. * **Google Analytics** - Zero-effort Angular Router awareness in Google Analytics. * **Router Guards** - Guard your Angular routes with built-in Firebase Authentication checks. ## Example use [](#example-use) import { provideFirebaseApp, initializeApp } from '@angular/fire/app'; import { getFirestore, provideFirestore } from '@angular/fire/firestore'; export const appConfig: ApplicationConfig \= { providers: \[ provideFirebaseApp(() \=> initializeApp({ ... })), provideFirestore(() \=> getFirestore()), ... \], ... }) import { AsyncPipe } from '@angular/common'; import { inject } from '@angular/core'; import { Firestore, collectionData, collection } from '@angular/fire/firestore'; interface Item { name: string, ... }; @Component({ selector: 'app-root', template: \` <ul> @for (item of (item$ | async); track item) { <li> {{ item.name }} </li> } </ul> \`, imports: \[AsyncPipe\] }) export class AppComponent { firestore \= inject(Firestore); itemCollection \= collection(this.firestore, 'items'); item$ \= collectionData<Item\>(itemCollection); } ## Resources [](#resources) [Quickstart](https://github.com/angular/angularfire/blob/main/docs/install-and-setup.md) - Get your first application up and running by following our quickstart guide. [Contributing](https://github.com/angular/angularfire/blob/main/CONTRIBUTING.md) [Stackblitz Template](https://stackblitz.com/edit/angular-fire-start) - Remember to set your Firebase configuration in `app/app.module.ts`. [Upgrading from v6.0? Check out our guide.](https://github.com/angular/angularfire/blob/main/docs/version-7-upgrade.md) ### Sample app [](#sample-app) The [`sample`](https://github.com/angular/angularfire/blob/main/sample) folder contains a kitchen sink application that demonstrates use of the "modular" API, in a zoneless server-rendered application, with all the bells and whistles. ### Having troubles? [](#having-troubles) Get help on our [Q&A board](https://github.com/angular/angularfire/discussions?discussions_q=category%3AQ%26A), the official [Firebase Mailing List](https://groups.google.com/forum/#!forum/firebase-talk), the [Firebase Community Slack](https://firebase.community/) (`#angularfire2`), the [Angular Community Discord](http://discord.gg/angular) (`#firebase`), [Gitter](https://gitter.im/angular/angularfire2), the [Firebase subreddit](https://www.reddit.com/r/firebase), or [Stack Overflow](https://stackoverflow.com/questions/tagged/angularfire2). > **NOTE:** While relatively stable, AngularFire is a [developer preview](https://angular.io/guide/releases#developer-preview) and is subject to change before general availability. Questions on the mailing list and issues filed here are answered on a **best-effort basis** by maintainers and other community members. If you are able to reproduce a problem with Firebase _outside of AngularFire's implementation_, please [file an issue on the Firebase JS SDK](https://github.com/firebase/firebase-js-sdk/issues) or reach out to the personalized [Firebase support channel](https://firebase.google.com/support/). ## Developer Guide [](#developer-guide) This developer guide assumes you're using the new tree-shakable AngularFire API, [if you're looking for the compatibility API you can find the documentation here](https://github.com/angular/angularfire/blob/main/docs/compat.md). [See the v7 upgrade guide for more information on this change.](https://github.com/angular/angularfire/blob/main/docs/version-7-upgrade.md). ### Firebase product integrations [](#firebase-product-integrations) <table><tbody><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/analytics.md#analytics">Analytics</a></h4><a id="user-content-analytics" aria-label="Permalink: Analytics" href="#analytics"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/analytics';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/analytics'</span><span>;</span></pre></div></td><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/auth.md#authentication">Authentication</a></h4><a id="user-content-authentication" aria-label="Permalink: Authentication" href="#authentication"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/auth';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/auth'</span><span>;</span></pre></div></td></tr><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/firestore.md#cloud-firestore">Cloud Firestore</a></h4><a id="user-content-cloud-firestore" aria-label="Permalink: Cloud Firestore" href="#cloud-firestore"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/firestore';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/firestore'</span><span>;</span></pre></div></td><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/functions.md#cloud-functions">Cloud Functions</a></h4><a id="user-content-cloud-functions" aria-label="Permalink: Cloud Functions" href="#cloud-functions"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/functions';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/functions'</span><span>;</span></pre></div></td></tr><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/messaging.md#cloud-messaging">Cloud Messaging</a></h4><a id="user-content-cloud-messaging" aria-label="Permalink: Cloud Messaging" href="#cloud-messaging"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/messaging';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/messaging'</span><span>;</span></pre></div></td><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/storage.md#cloud-storage">Cloud Storage</a></h4><a id="user-content-cloud-storage" aria-label="Permalink: Cloud Storage" href="#cloud-storage"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/storage';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/storage'</span><span>;</span></pre></div></td></tr><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/performance.md#performance-monitoring">Performance Monitoring</a></h4><a id="user-content-performance-monitoring" aria-label="Permalink: Performance Monitoring" href="#performance-monitoring"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/performance';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/performance'</span><span>;</span></pre></div></td><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/database.md#realtime-database">Realtime Database</a></h4><a id="user-content-realtime-database" aria-label="Permalink: Realtime Database" href="#realtime-database"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/database';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/database'</span><span>;</span></pre></div></td></tr><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/remote-config.md#remote-config">Remote Config</a></h4><a id="user-content-remote-config" aria-label="Permalink: Remote Config" href="#remote-config"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/remote-config';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/remote-config'</span><span>;</span></pre></div></td><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/app-check.md#app-check">App Check</a></h4><a id="user-content-app-check" aria-label="Permalink: App Check" href="#app-check"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/app-check';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/app-check'</span><span>;</span></pre></div></td></tr><tr><td><p dir="auto"></p><h4 tabindex="-1" dir="auto"><a href="https://github.com/angular/angularfire/blob/main/docs/vertexai.md#vertex-ai">Vertex AI</a></h4><a id="user-content-vertex-ai" aria-label="Permalink: Vertex AI" href="#vertex-ai"></a><p></p><div dir="auto" data-snippet-clipboard-copy-content="import { } from '@angular/fire/vertexai';"><pre><span>import</span> <span>{</span> <span>}</span> <span>from</span> <span>'@angular/fire/vertexai'</span><span>;</span></pre></div></td></tr></tbody></table> --- ## Page: https://github.com/angular/components/tree/main/src/google-maps#readme * * [ GitHub Copilot Write better code with AI ](https://github.com/features/copilot) * [ GitHub Advanced Security Find and fix vulnerabilities ](https://github.com/security/advanced-security) * [ Actions Automate any workflow ](https://github.com/features/actions) * [ Codespaces Instant dev environments ](https://github.com/features/codespaces) * [ Issues Plan and track work ](https://github.com/features/issues) * [ Code Review Manage code changes ](https://github.com/features/code-review) * [ Discussions Collaborate outside of code ](https://github.com/features/discussions) * [ Code Search Find more, search less ](https://github.com/features/code-search) * Explore * [Learning Pathways](https://resources.github.com/learn/pathways) * [Events & Webinars](https://resources.github.com/) * [Ebooks & Whitepapers](https://github.com/resources/whitepapers) * [Customer Stories](https://github.com/customer-stories) * [Partners](https://partner.github.com/) * [Executive Insights](https://github.com/solutions/executive-insights) * * [ GitHub Sponsors Fund open source developers ](https://github.com/sponsors) * [ The ReadME Project GitHub community articles ](https://github.com/readme) * * [ Enterprise platform AI-powered developer platform ](https://github.com/enterprise) * [Pricing](https://github.com/pricing) ## Provide feedback ## Saved searches ## Use saved searches to filter your results more quickly [Sign up](https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Ffiles%2Fdisambiguate&source=header-repo&source_repo=angular%2Fcomponents) --- ## Page: https://github.com/google-pay/google-pay-button#angular ## The Google Pay button [](#the-google-pay-button) The [Google Pay API](https://developers.google.com/pay/api/web/overview) enables fast, simple checkout on your website. This provides convenient access to hundreds of millions of cards that are saved to Google Accounts worldwide. See Google Pay in action: [](https://developers.google.com/pay/api/web/guides/resources/demos) This repository contains Google Pay button implementations for compatible with popular website frameworks even easier. ## Web [](#web) This is a bare bones, plain vanilla JavaScript implementation of the Google Pay button. These examples are designed to launch into a Project IDX Workspace, ready to run. * [Example code basic](https://github.com/google-pay/google-pay-button/blob/main/examples/html/gpay-web-101) [](https://goo.gle/4fnRl4N) * [Example code advanced](https://github.com/google-pay/google-pay-button/blob/main/examples/html/gpay-web-201) [](https://goo.gle/3YPHFce) ## Web component [](#web-component) [](https://www.npmjs.com/package/@google-pay/button-element) The [Google Pay web component button](https://github.com/google-pay/google-pay-button/blob/main/src/button-element) makes it easy to integrate Google Pay into your website using standards based custom elements. Web components can be used directly in a standard HTML web application as is, and is also [compatible with many popular web frameworks](https://custom-elements-everywhere.com/). * [Example Vue Google Pay button](https://github.com/google-pay/google-pay-button/blob/main/examples/vue) * [Example Svelte Google Pay button](https://github.com/google-pay/google-pay-button/blob/main/examples/svelte) npm install @google-pay/button-element Find out more about the [Google Pay web component button](https://github.com/google-pay/google-pay-button/blob/main/src/button-element). ## React [](#react) [](https://www.npmjs.com/package/@google-pay/button-react) Web components are more difficult to consume in a React application due to the extra work involved in binding to web component properties with React. A separate [Google Pay React button](https://github.com/google-pay/google-pay-button/blob/main/src/button-react) has been created to make it easy to integrate Google Pay into your React website. npm install @google-pay/button-react Find out more about the [Google Pay React button](https://github.com/google-pay/google-pay-button/blob/main/src/button-react). ## Angular [](#angular) [](https://www.npmjs.com/package/@google-pay/button-angular) An Angular version of the [Google Pay button](https://github.com/google-pay/google-pay-button/blob/main/src/button-angular) has been created to make it easier to integrate Google Pay into your Angular website. The advantage of using the Angular version of the Google Pay button over the web component is that it eliminates the need to register `CUSTOM_ELEMENTS_SCHEMA`. npm install @google-pay/button-angular Find out more about the [Google Pay Angular button](https://github.com/google-pay/google-pay-button/blob/main/src/button-angular). ## Other frameworks [](#other-frameworks) The intention is for the web component to support other web frameworks. Support for additional framework specific libraries will be considered based on demand. ## Have any questions? [](#have-any-questions) Ask it on the [discussions](https://github.com/google-pay/google-pay-button/discussions) section of the Google Pay button project. --- ## Page: https://github.com/angular/components/blob/main/src/youtube-player/README.md * * [ GitHub Copilot Write better code with AI ](https://github.com/features/copilot) * [ GitHub Advanced Security Find and fix vulnerabilities ](https://github.com/security/advanced-security) * [ Actions Automate any workflow ](https://github.com/features/actions) * [ Codespaces Instant dev environments ](https://github.com/features/codespaces) * [ Issues Plan and track work ](https://github.com/features/issues) * [ Code Review Manage code changes ](https://github.com/features/code-review) * [ Discussions Collaborate outside of code ](https://github.com/features/discussions) * [ Code Search Find more, search less ](https://github.com/features/code-search) * Explore * [Learning Pathways](https://resources.github.com/learn/pathways) * [Events & Webinars](https://resources.github.com/) * [Ebooks & Whitepapers](https://github.com/resources/whitepapers) * [Customer Stories](https://github.com/customer-stories) * [Partners](https://partner.github.com/) * [Executive Insights](https://github.com/solutions/executive-insights) * * [ GitHub Sponsors Fund open source developers ](https://github.com/sponsors) * [ The ReadME Project GitHub community articles ](https://github.com/readme) * * [ Enterprise platform AI-powered developer platform ](https://github.com/enterprise) * [Pricing](https://github.com/pricing) ## Provide feedback ## Saved searches ## Use saved searches to filter your results more quickly [Sign up](https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow&source=header-repo&source_repo=angular%2Fcomponents)