Browse Source

Task/update angular-developer skills (20260917) (#7893)

Update angular-developer skills
pull/7800/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
8718b53468
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      .agents/skills/angular-developer/SKILL.md
  2. 24
      .agents/skills/angular-developer/references/angular-aria.md
  3. 18
      .agents/skills/angular-developer/references/components.md
  4. 38
      .agents/skills/angular-developer/references/creating-services.md
  5. 18
      .agents/skills/angular-developer/references/define-routes.md
  6. 6
      .agents/skills/angular-developer/references/effects.md
  7. 7
      .agents/skills/angular-developer/references/environment-configuration.md
  8. 4
      .agents/skills/angular-developer/references/loading-strategies.md
  9. 22
      .agents/skills/angular-developer/references/mcp.md
  10. 76
      .agents/skills/angular-developer/references/naming-conventions.md
  11. 12
      .agents/skills/angular-developer/references/pipes.md
  12. 15
      .agents/skills/angular-developer/references/router-testing.md
  13. 6
      skills-lock.json

9
.agents/skills/angular-developer/SKILL.md

@ -1,6 +1,6 @@
--- ---
name: angular-developer name: angular-developer
description: Generates Angular code and provides architectural guidance. Trigger when creating projects, components, services, or HTTP communication, or for best practices on reactivity (signals, linkedSignal, resource, httpResource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling. description: Generates Angular code and provides architectural guidance. Trigger when creating projects, components, services, or HTTP communication, or for best practices on reactivity (signals, linkedSignal, resource, httpResource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, naming conventions, or CLI tooling.
license: MIT license: MIT
metadata: metadata:
author: Copyright 2026 Google LLC author: Copyright 2026 Google LLC
@ -20,7 +20,7 @@ metadata:
If no guidelines are provided by the user, here are some default rules to follow when creating a new Angular project: If no guidelines are provided by the user, here are some default rules to follow when creating a new Angular project:
1. Use the latest stable version of Angular unless the user specifies otherwise. 1. Use the latest stable version of Angular unless the user specifies otherwise.
2. Use Signals Forms for form management in new projects (available in Angular v21 and newer) [Find out more](references/signal-forms.md). 2. Use Signal Forms for form management in new projects (stable in Angular v22 and newer) [Find out more](references/signal-forms.md).
**Execution Rules for `ng new`:** **Execution Rules for `ng new`:**
When asked to create a new Angular project, you must determine the correct execution command by following these strict steps: When asked to create a new Angular project, you must determine the correct execution command by following these strict steps:
@ -45,10 +45,11 @@ When asked to create a new Angular project, you must determine the correct execu
When working with Angular components, consult the following references based on the task: When working with Angular components, consult the following references based on the task:
- **Fundamentals**: Anatomy, metadata, core concepts, and template control flow (@if, @for, @switch). Read [components.md](references/components.md) - **Fundamentals**: Anatomy, metadata, core concepts, self-closing tags, and template control flow (@if, @for, @switch). Read [components.md](references/components.md)
- **Inputs**: Signal-based inputs, transforms, and model inputs. Read [inputs.md](references/inputs.md) - **Inputs**: Signal-based inputs, transforms, and model inputs. Read [inputs.md](references/inputs.md)
- **Outputs**: Signal-based outputs and custom event best practices. Read [outputs.md](references/outputs.md) - **Outputs**: Signal-based outputs and custom event best practices. Read [outputs.md](references/outputs.md)
- **Host Elements**: Host bindings and attribute injection. Read [host-elements.md](references/host-elements.md) - **Host Elements**: Host bindings and attribute injection. Read [host-elements.md](references/host-elements.md)
- **Naming Conventions**: Modern Angular v20+ naming style ("Intent over Role") for files, components, services, directives, pipes, and models. Read [naming-conventions.md](references/naming-conventions.md)
If you require deeper documentation not found in the references above, read the documentation at `https://angular.dev/guide/components`. If you require deeper documentation not found in the references above, read the documentation at `https://angular.dev/guide/components`.
@ -71,7 +72,7 @@ When communicating with backend services, use Angular HTTP APIs and consult the
In most cases for new apps, **prefer signal forms**. When making a forms decision, analyze the project and consider the following guidelines: In most cases for new apps, **prefer signal forms**. When making a forms decision, analyze the project and consider the following guidelines:
- If the application is using v21 or newer and this is a new form, **prefer signal forms**. - If the application is using v22 or newer and this is a new form, **prefer Signal Forms**.
- For older applications or when working with existing forms, use the appropriate form type that matches the applications current form strategy. - For older applications or when working with existing forms, use the appropriate form type that matches the applications current form strategy.
- **Signal Forms**: Use signals for form state management. Read [signal-forms.md](references/signal-forms.md) - **Signal Forms**: Use signals for form state management. Read [signal-forms.md](references/signal-forms.md)

24
.agents/skills/angular-developer/references/angular-aria.md

@ -17,7 +17,7 @@ Common ARIA attributes to target in CSS:
--- ---
**CRITICAL**: Before using this package, it must be installed via the package manager. Confirm that it has been installed in the project. Use `npm install @angular/aria` to install if necessary. **CRITICAL**: Before using this package, confirm that `@angular/aria` is installed. If necessary, install it using the package manager configured for the project.
## 1. Accordion ## 1. Accordion
@ -331,9 +331,25 @@ Groups related controls (like text formatting).
```html ```html
<div ngToolbar class="toolbar"> <div ngToolbar class="toolbar">
<div ngToolbarWidgetGroup [multi]="true" role="group" aria-label="Formatting"> <div ngToolbarWidgetGroup role="group" aria-label="Formatting">
<button ngToolbarWidget value="bold" class="tool-btn">B</button> <button
<button ngToolbarWidget value="italic" class="tool-btn">I</button> ngToolbarWidget
type="button"
[attr.aria-pressed]="bold()"
(click)="bold.set(!bold())"
class="tool-btn"
>
B
</button>
<button
ngToolbarWidget
type="button"
[attr.aria-pressed]="italic()"
(click)="italic.set(!italic())"
class="tool-btn"
>
I
</button>
</div> </div>
</div> </div>
``` ```

18
.agents/skills/angular-developer/references/components.md

@ -48,6 +48,24 @@ To use a component, add it to the `imports` array of the consuming component and
export class App {} export class App {}
``` ```
### Self-Closing Tags
Angular supports self-closing tags for custom components.
**Rule:** Always use self-closing tags when a component does not contain projected content or child nodes:
```html
<!-- Preferred: concise and modern -->
<app-profile />
<app-user-card [user]="currentUser()" />
<router-outlet />
<!-- Avoid: redundant closing tags for empty elements -->
<app-profile></app-profile>
<app-user-card [user]="currentUser()"></app-user-card>
<router-outlet></router-outlet>
```
## Template Control Flow ## Template Control Flow
Angular uses built-in blocks for conditional rendering and loops. Angular uses built-in blocks for conditional rendering and loops.

38
.agents/skills/angular-developer/references/creating-services.md

@ -10,21 +10,20 @@ You can generate a service using the Angular CLI:
ng generate service my-data ng generate service my-data
``` ```
Or you can manually create a TypeScript class and decorate it with `@Service()`. Or you can manually create a TypeScript class and decorate it with `@Service()`. For reactive state management, store data in a private `signal()` and expose it publicly via `.asReadonly()`:
```ts ```ts
import {Service} from '@angular/core'; import {Service, signal} from '@angular/core';
@Service() @Service()
export class BasicDataStore { export class BasicDataStore {
private data: string[] = []; private readonly dataSignal = signal<string[]>([]);
addData(item: string): void { // Expose state as a read-only signal to prevent direct external mutation
this.data.push(item); readonly data = this.dataSignal.asReadonly();
}
getData(): string[] { addData(item: string): void {
return [...this.data]; this.dataSignal.update((items) => [...items, item]);
} }
} }
``` ```
@ -39,7 +38,7 @@ Using `@Service` is the recommended approach for most services. It tells Angular
#### The `autoProvided` option #### The `autoProvided` option
If you don't want to create a singleton of your service, you can set `@Service({autoProvided: false})` and declare the service a `providers` array. If you don't want to create a singleton of your service, you can set `@Service({autoProvided: false})` and declare the service in a `providers` array.
## Injecting a Service ## Injecting a Service
@ -55,36 +54,33 @@ import {BasicDataStore} from './basic-data-store.service';
selector: 'app-example', selector: 'app-example',
template: ` template: `
<div> <div>
<p>Data items: {{ dataStore.getData().length }}</p> <p>Data items: {{ dataStore.data().length }}</p>
<button (click)="dataStore.addData('New Item')">Add Item</button> <button (click)="dataStore.addData('New Item')">Add Item</button>
</div> </div>
`, `,
}) })
export class Example { export class Example {
// Inject the service as a class field // Inject the service as a class field
dataStore = inject(BasicDataStore); readonly dataStore = inject(BasicDataStore);
} }
``` ```
### Injecting into Another Service ### Injecting into Another Service
Services can inject other services in the exact same way. Services can inject other services in the exact same way. Use `computed()` to derive values from injected services reactively:
```ts ```ts
import {Injectable, inject} from '@angular/core'; import {Service, computed, inject, signal} from '@angular/core';
import {AdvancedDataStore} from './advanced-data-store.service'; import {AdvancedDataStore} from './advanced-data-store.service';
@Service() @Service()
export class BasicDataStore { export class CombinedDataStore {
// Injecting another service // Injecting another service
private advancedDataStore = inject(AdvancedDataStore); private readonly advancedDataStore = inject(AdvancedDataStore);
private readonly dataSignal = signal<string[]>([]);
private data: string[] = [];
getData(): string[] { // Combine reactive state from this service and the injected service
// Combine data from this service and the injected service readonly allData = computed(() => [...this.dataSignal(), ...this.advancedDataStore.data()]);
return [...this.data, ...this.advancedDataStore.getData()];
}
} }
``` ```

18
.agents/skills/angular-developer/references/define-routes.md

@ -38,6 +38,24 @@ Use `redirectTo` to point one path to another.
{ path: 'blog', component: Blog }, { path: 'blog', component: Blog },
``` ```
### Conditional Redirects
Pass a function to `redirectTo` to apply logic when redirecting.
```ts
{
path: 'restaurant/:location/menu',
redirectTo: ({ params }) => {
const base = `/restaurant/${params['location']}/menu`;
const hour = new Date().getHours();
if (hour < 11) return `${base}/breakfast`;
if (hour < 17) return `${base}/lunch`;
return `${base}/dinner`;
},
},
```
## Page Titles ## Page Titles
Associate titles with routes for accessibility. Titles can be static or dynamic (via `ResolveFn` or a custom `TitleStrategy`). Associate titles with routes for accessibility. Titles can be static or dynamic (via `ResolveFn` or a custom `TitleStrategy`).

6
.agents/skills/angular-developer/references/effects.md

@ -55,7 +55,7 @@ import { Component, afterRenderEffect, viewChild, ElementRef } from '@angular/co
@Component({...}) @Component({...})
export class Chart { export class Chart {
canvas = viewChild.required<ElementRef>('canvas'); canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
constructor() { constructor() {
afterRenderEffect({ afterRenderEffect({
@ -63,10 +63,10 @@ export class Chart {
earlyRead: () => { earlyRead: () => {
return this.canvas().nativeElement.getBoundingClientRect().width; return this.canvas().nativeElement.getBoundingClientRect().width;
}, },
// 2. Write to the DOM (receives the result of the previous phase) // 2. Write to the DOM (receives the previous phase result as a Signal)
write: (width) => { write: (width) => {
// NEVER read from the DOM in the write phase. // NEVER read from the DOM in the write phase.
setupChart(this.canvas().nativeElement, width); setupChart(this.canvas().nativeElement, width());
} }
}); });
} }

7
.agents/skills/angular-developer/references/environment-configuration.md

@ -81,10 +81,15 @@ Load the configuration before the application starts:
```ts ```ts
import {Service, inject} from '@angular/core'; import {Service, inject} from '@angular/core';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {tap} from 'rxjs';
interface AppConfig {
apiUrl: string;
}
@Service() @Service()
export class AppConfigService { export class AppConfigService {
private config!: {apiUrl: string}; private config!: AppConfig;
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);

4
.agents/skills/angular-developer/references/loading-strategies.md

@ -24,7 +24,7 @@ Use `loadComponent` to fetch the component on demand.
```ts ```ts
{ {
path: 'admin', path: 'admin',
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent)`, loadComponent: () => import('./admin').then(m => m.Admin),
} }
``` ```
@ -39,6 +39,8 @@ Use `loadChildren` to fetch a set of routes.
} }
``` ```
Return the `import()` promise directly only when the loaded file uses a `default` export.
## Injection Context and Lazy Loading ## Injection Context and Lazy Loading
Loader functions run within the **injection context** of the current route. This allows you to call `inject()` to make context-aware loading decisions. Loader functions run within the **injection context** of the current route. This allows you to call `inject()` to make context-aware loading decisions.

22
.agents/skills/angular-developer/references/mcp.md

@ -9,24 +9,15 @@ When the MCP server is enabled, AI agents have access to the following tools:
| Name | Description | | Name | Description |
| :-------------------------- | :-------------------------------------------------------------------------------------------------------- | | :-------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `ai_tutor` | Launches an interactive AI-powered Angular tutor. | | `ai_tutor` | Launches an interactive AI-powered Angular tutor. |
| `devserver.start` | Asynchronously starts a dev server (`ng serve`). Returns immediately. |
| `devserver.stop` | Stops the dev server. |
| `devserver.wait_for_build` | Returns the logs of the most recent build in a running dev server. |
| `get_best_practices` | Retrieves the Angular Best Practices Guide (crucial for standalone components, typed forms, etc.). | | `get_best_practices` | Retrieves the Angular Best Practices Guide (crucial for standalone components, typed forms, etc.). |
| `list_projects` | Lists all applications and libraries in the workspace by reading `angular.json`. | | `list_projects` | Lists all applications and libraries in the workspace by reading `angular.json`. |
| `onpush_zoneless_migration` | Analyzes code and provides a plan to migrate it to `OnPush` change detection (prerequisite for zoneless). | | `onpush_zoneless_migration` | Analyzes code and provides a plan to migrate it to `OnPush` change detection (prerequisite for zoneless). |
| `run_target` | Executes a configured target. |
| `search_documentation` | Searches the official documentation at `https://angular.dev`. | | `search_documentation` | Searches the official documentation at `https://angular.dev`. |
## Experimental Tools
Some tools must be enabled explicitly using the `--experimental-tool` (or `-E`) flag.
| Name | Description |
| :------------------------- | :-------------------------------------------------------------------- |
| `build` | Performs a one-off build using `ng build`. |
| `devserver.start` | Asynchronously starts a dev server (`ng serve`). Returns immediately. |
| `devserver.stop` | Stops the dev server. |
| `devserver.wait_for_build` | Returns the logs of the most recent build in a running dev server. |
| `e2e` | Executes end-to-end tests. |
| `test` | Runs the project's unit tests. |
## Configuration ## Configuration
To use the MCP server, you configure your host environment (IDE or CLI) to run `npx @angular/cli mcp`. To use the MCP server, you configure your host environment (IDE or CLI) to run `npx @angular/cli mcp`.
@ -97,10 +88,9 @@ You can pass arguments to the MCP server in the `args` array of your configurati
- `--read-only`: Only registers tools that do not modify the project. - `--read-only`: Only registers tools that do not modify the project.
- `--local-only`: Only registers tools that do not require an internet connection. - `--local-only`: Only registers tools that do not require an internet connection.
- `--experimental-tool` (`-E`): Enables specific experimental tools (e.g., `-E build`, `-E devserver`).
Example for read-only mode with experimental tools enabled: Example for read-only mode:
```json ```json
"args": ["-y", "@angular/cli", "mcp", "--read-only", "-E", "build", "-E", "test"] "args": ["-y", "@angular/cli", "mcp", "--read-only"]
``` ```

76
.agents/skills/angular-developer/references/naming-conventions.md

@ -0,0 +1,76 @@
# Angular Naming Conventions (Angular v20+ Style Guide)
This skill enforces Angular naming conventions for components, services, directives, pipes, and models. While it promotes the modern **"Intent over Role"** philosophy introduced in Angular v20, **it must respect existing project configurations first**.
---
## Core Principles
1. **Prioritize Existing Conventions**: Before generating or refactoring files, check the existing project files, `angular.json` configuration, and ESLint rules. **Do not force suffixless naming on projects that rely on standard suffixes.**
2. **Remove Role Suffixes (Modern Projects Only)**: In projects configured for "Intent over Role" or newly bootstrapped v20+ projects, filenames no longer include functional extensions like `.component.ts`, `.service.ts`, or `.directive.ts`. Corresponding TypeScript classes drop suffixes like `Component`, `Service`, or `Directive`.
3. **Intent/Purpose-Based Naming**: When suffixless naming is active, name files and classes based on their specific domain, responsibility, or business purpose (e.g., `-data`, `-store`, `-api`, or `-formatter`).
4. **Folder Location as Context**: Lean on folder hierarchy (`core/`, `features/`, `shared/`) and IDE capabilities to identify the technical role of files, rather than encoding that context within the file name.
5. **Interface/Model Exception**: Interfaces and data models still retain the `.model.ts` suffix to clearly declare type contracts.
---
## Recommended Project Structure & Naming Rules
### 1. File/Identifier Matching & Consistency
- **Hyphens in Filenames**: Continue using kebab-case (hyphens) to separate words in filenames (e.g., `product-list.ts`).
- **Identifier Matching**: Filenames must align directly with the primary TypeScript class/identifier (e.g., `product-list.ts` contains `class ProductList`).
- **Unified Filenames**: If using split template or style files, keep names identical to the main TypeScript file:
- `product-list.ts`
- `product-list.html`
- `product-list.css`
- **Test Files**: Continue to use the same base name with the `.spec.ts` suffix (e.g., `product-list.spec.ts` for `product-list.ts`).
### 2. Core Directory (Application Foundation)
Houses singleton services, global state, and system-wide models.
- **Services (Logic/State)**:
- _Old_: `auth.service.ts` (Class: `AuthService`)
- _New_: `auth.ts` (Class: `AuthService`)
- _Alternative (Intent-specific)_: Use descriptive domain-purpose suffixes like `[domain]-data.ts`, `[domain]-store.ts`, or `[domain]-data-client.ts` (e.g., `auth-data.ts` / `AuthData`, `user-data-client.ts` / `UserDataClient`).
- **Models**: Retain the `.model.ts` suffix for data shapes.
- _Example_: `user.model.ts` (Interface: `User`)
### 3. Features Directory (Domain Business Logic)
Organize files into feature-specific folders containing components, local services, and routes related to that domain.
- **Main Feature Component**: Name the main feature component after the route or feature itself.
- _Example_: `features/profile/profile.ts` (Class: `Profile`)
- **Feature Sub-Components**: Name sub-components based on their display or functional role.
- _Example_: `features/profile/components/profile-header.ts` (Class: `ProfileHeader`)
- _Example_: `features/projects/components/project-card.ts` (Class: `ProjectCard`)
- **Feature Services**: Name feature services based on feature-specific data or state needs.
- _Example_: `features/projects/projects-data.ts` (Class: `ProjectsData`)
### 4. Shared Directory (Reusable UI Toolkit)
Store pure, presentational elements and helpers with zero business logic in a shared folder.
- **Shared Components**: Name shared components based on their reusable UI role.
- _Example_: `shared/components/button/button.ts` (Class: `Button`)
- _Example_: `shared/components/spinner/spinner.ts` (Class: `Spinner`)
- **Shared Pipes**: Name shared pipes according to their formatting purpose.
- _Example_: `shared/pipes/format-date.ts` (Class: `FormatDate`)
- **Shared Directives**: Name directives according to the behavior they attach to elements.
- _Old_: `highlight.directive.ts` (Class: `HighlightDirective`)
- _New_: `highlight.ts` (Class: `Highlight`)
---
## Best Practices & Coexistence Rules
- **How to Determine the Style in Use**:
1. Inspect adjacent files in the target directory (do they end in `.component.ts` or `.ts`?).
2. Check `angular.json` for custom schematics options that might configure suffix behaviors.
3. If unsure, use the traditional role suffix style (`.component.ts`, `.service.ts`) as it is the safest default in the Angular ecosystem.
- **Avoid Namespace Collisions**: Without role suffixes, files like `user.ts` (component) and `user.model.ts` (model) can collide if they both declare a class/interface named `User`.
- To prevent this use more specific, intent-based names for components (e.g. `class UserProfile` in `user-profile.ts` or `class UserDetail` in `user-detail.ts`) while keeping the simple domain name for the interface (`interface User` in `user.model.ts`).
- **Consistency Check**: Do not mix old suffix styles and new suffixless styles in the same feature folder or module. Keep existing legacy code as-is unless migrating the entire module to the modern structure.
- **Lean on the IDE**: Rely on modern IDE code navigation (e.g., "Go to Definition" or fuzzy searches for class names like `AuthData` or `ProfileHeader`) and file type icons rather than visual scan of suffix strings.

12
.agents/skills/angular-developer/references/pipes.md

@ -54,10 +54,10 @@ export class KebabCasePipe implements PipeTransform {
```ts ```ts
// formatter.service.ts — import the function, NOT the pipe // formatter.service.ts — import the function, NOT the pipe
import {Injectable} from '@angular/core'; import {Service} from '@angular/core';
import {toKebabCase} from './kebab-case'; import {toKebabCase} from './kebab-case';
@Injectable({providedIn: 'root'}) @Service()
export class FormatterService { export class FormatterService {
toSlug(title: string): string { toSlug(title: string): string {
return toKebabCase(title); return toKebabCase(title);
@ -80,10 +80,10 @@ Inject `LOCALE_ID` to get the current locale and pass it to the function.
```ts ```ts
// CORRECT — use formatNumber instead of injecting DecimalPipe // CORRECT — use formatNumber instead of injecting DecimalPipe
import {Injectable, LOCALE_ID, inject} from '@angular/core'; import {Service, LOCALE_ID, inject} from '@angular/core';
import {formatNumber} from '@angular/common'; import {formatNumber} from '@angular/common';
@Injectable({providedIn: 'root'}) @Service()
export class PriceService { export class PriceService {
private locale = inject(LOCALE_ID); private locale = inject(LOCALE_ID);
@ -95,10 +95,10 @@ export class PriceService {
```ts ```ts
// WRONG — do not inject pipe classes // WRONG — do not inject pipe classes
import {Injectable} from '@angular/core'; import {Service, inject} from '@angular/core';
import {DecimalPipe} from '@angular/common'; import {DecimalPipe} from '@angular/common';
@Injectable({providedIn: 'root'}) @Service()
export class PriceService { export class PriceService {
// ❌ DecimalPipe is not designed to be injected // ❌ DecimalPipe is not designed to be injected
private pipe = inject(DecimalPipe); private pipe = inject(DecimalPipe);

15
.agents/skills/angular-developer/references/router-testing.md

@ -12,7 +12,7 @@ The `RouterTestingHarness` is the primary tool for testing routing scenarios. Yo
```ts ```ts
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {provideRouter} from '@angular/router'; import {provideRouter, Router} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing'; import {RouterTestingHarness} from '@angular/router/testing';
import {Dashboard} from './dashboard.component'; import {Dashboard} from './dashboard.component';
import {HeroDetail} from './hero-detail.component'; import {HeroDetail} from './hero-detail.component';
@ -41,7 +41,7 @@ describe('Dashboard Component Routing', () => {
### Key Concepts ### Key Concepts
1. **`provideRouter([...])`**: Provide a test-specific routing configuration. This should include the routes necessary for the component-under-test to function correctly. 1. **`provideRouter([...])`**: Provide a test-specific routing configuration. This should include the routes necessary for the component-under-test to function correctly.
2. **`RouterTestingHarness.create()`**: Asynchronously creates and initializes the harness and performs an initial navigation to the root URL (`/`). 2. **`RouterTestingHarness.create(initialUrl?)`**: Asynchronously creates the harness and optionally performs an initial navigation.
## Writing Router Tests ## Writing Router Tests
@ -62,13 +62,14 @@ it('should navigate to a hero detail when a hero is selected', async () => {
await harness.fixture.whenStable(); await harness.fixture.whenStable();
// 2. Assert on the URL // 2. Assert on the URL
expect(harness.router.url).toEqual('/heroes/42'); const router = TestBed.inject(Router);
expect(router.url).toEqual('/heroes/42');
// 3. Get the activated component after navigation // 3. Get the activated component after navigation
const heroDetail = await harness.getHarness(HeroDetail); const heroDetail = harness.routeDebugElement?.componentInstance as HeroDetail;
// 4. Assert on the state of the new component // 4. Assert on the state of the new component
expect(await heroDetail.componentInstance.hero.name).toBe('Test Hero'); expect(heroDetail.hero.name).toBe('Test Hero');
}); });
it('should get the activated component directly', async () => { it('should get the activated component directly', async () => {
@ -82,6 +83,6 @@ it('should get the activated component directly', async () => {
### Best Practices ### Best Practices
- **Navigate with the Harness:** Always use `harness.navigateByUrl()` to simulate navigation. This method returns a promise that resolves with the instance of the activated component. - **Navigate with the Harness:** Always use `harness.navigateByUrl()` to simulate navigation. This method returns a promise that resolves with the instance of the activated component.
- **Access the Router State:** Use `harness.router` to access the live router instance and assert on its state (e.g., `harness.router.url`). - **Access the Router State:** Inject `Router` from `TestBed` to inspect the live router state.
- **Get Activated Components:** Use `harness.getHarness(ComponentType)` to get an instance of a component harness for the currently activated routed component, or `harness.routeDebugElement` to get the `DebugElement`. - **Get Activated Components:** Use the component returned by `navigateByUrl(url, ComponentType)`. After application-driven navigation, read `harness.routeDebugElement?.componentInstance`.
- **Wait for Stability:** After performing an action that causes navigation, always `await harness.fixture.whenStable()` to ensure the routing is complete before making assertions. - **Wait for Stability:** After performing an action that causes navigation, always `await harness.fixture.whenStable()` to ensure the routing is complete before making assertions.

6
skills-lock.json

@ -2,10 +2,10 @@
"version": 1, "version": 1,
"skills": { "skills": {
"angular-developer": { "angular-developer": {
"source": "angular/skills", "source": "angular/angular",
"sourceType": "github", "sourceType": "github",
"skillPath": "angular-developer/SKILL.md", "skillPath": "skills/dev-skills/angular-developer/SKILL.md",
"computedHash": "ded1e95fb8d75d60901201665c6ab7e348eec3b79a6c53824c4a30a487d99f58" "computedHash": "0850aa966b6a7da1151daa9d0ef2081bf397f052573a35c1643fb92b948f0f00"
}, },
"karpathy-guidelines": { "karpathy-guidelines": {
"source": "multica-ai/andrej-karpathy-skills", "source": "multica-ai/andrej-karpathy-skills",

Loading…
Cancel
Save