5.Angular 应用实践


文档摘要

5.Angular 应用实践 Angular 应用实践章节 5.1 组件化架构:构建可复用和可维护的用户界面 组件化是 Angular 的核心架构理念,它将用户界面分解为独立的、可复用的组件。每个组件封装了自己的逻辑、模板和样式,使得应用结构清晰、易于管理和测试。 5.1.1 组件的优势 可复用性 (Reusability): 组件可以在应用的多个地方甚至不同的应用中重复使用,减少代码冗余,提高开发效率。 可维护性 (Maintainability): 组件的独立性使得修改和维护更加容易,影响范围被限制在组件内部,降低了维护成本。 可测试性 (Testability): 每个组件都可以独立进行单元测试,确保组件的正确性,提高应用的整体质量。

# 5.Angular 应用实践 ## 5. Angular 应用实践章节 ### 5.1 组件化架构:构建可复用和可维护的用户界面 组件化是 Angular 的核心架构理念,它将用户界面分解为独立的、可复用的组件。每个组件封装了自己的逻辑、模板和样式,使得应用结构清晰、易于管理和测试。 **5.1.1 组件的优势** * **可复用性 (Reusability):** 组件可以在应用的多个地方甚至不同的应用中重复使用,减少代码冗余,提高开发效率。 * **可维护性 (Maintainability):** 组件的独立性使得修改和维护更加容易,影响范围被限制在组件内部,降低了维护成本。 * **可测试性 (Testability):** 每个组件都可以独立进行单元测试,确保组件的正确性,提高应用的整体质量。 * **关注点分离 (Separation of Concerns):** 组件将 UI、逻辑和样式分离,使得代码结构更清晰,更易于理解和协作。 **5.1.2 组件的结构** 一个 Angular 组件通常包含以下几个部分: * **模板 (Template):** 定义组件的 HTML 结构,使用 Angular 模板语法进行数据绑定和事件处理。 * **组件类 (Component Class):** 包含组件的业务逻辑、数据和事件处理方法。使用 TypeScript 编写,通过装饰器 `@Component` 定义。 * **样式 (Styles):** 定义组件的 CSS 样式,可以内联在组件中,也可以使用外部样式文件。 * **元数据 (Metadata):** 通过 `@Component` 装饰器配置组件的元数据,例如选择器 (selector)、模板路径 (templateUrl)、样式路径 (styleUrls) 等。 **5.1.3 代码实践:创建和使用组件** 我们创建一个简单的任务列表组件 `TaskListComponent` 来演示组件的创建和使用。 **taskList.component.ts:** ```typescript import { Component, Input, Output, EventEmitter } from '@angular/core'; interface Task { id: number; name: string; completed: boolean; } @Component({ selector: 'app-task-list', templateUrl: './task-list.component.html', styleUrls: ['./task-list.component.css'] }) export class TaskListComponent { @Input() tasks: Task[] = []; @Output() taskCompleted = new EventEmitter(); toggleComplete(taskId: number): void { this.taskCompleted.emit(taskId); } } ``` **taskList.component.html:** ```html
  • {{ task.name }}
``` **taskList.component.css:** ```css .completed { text-decoration: line-through; color: gray; } ``` **使用 `TaskListComponent` 的父组件 (例如 `AppComponent`):** **app.component.ts:** ```typescript import { Component } from '@angular/core'; interface Task { id: number; name: string; completed: boolean; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { tasks: Task[] = [ { id: 1, name: '学习 Angular 组件', completed: false }, { id: 2, name: '练习组件通信', completed: true }, { id: 3, name: '理解组件生命周期', completed: false } ]; onTaskCompleted(taskId: number): void { this.tasks = this.tasks.map(task => { if (task.id === taskId) { return { ...task, completed: !task.completed }; } return task; }); } } ``` **app.component.html:** ```html

我的任务列表

``` **代码详解:** * `TaskListComponent` 使用 `@Component` 装饰器定义,指定了选择器 `app-task-list`,模板和样式文件。 * `@Input() tasks: Task[]` 声明了输入属性 `tasks`,父组件可以通过属性绑定将任务列表数据传递给 `TaskListComponent`。 * `@Output() taskCompleted = new EventEmitter()` 声明了输出属性 `taskCompleted`,当任务完成状态改变时,子组件会通过 `EventEmitter` 发射事件,父组件可以监听该事件并处理。 * `toggleComplete(taskId: number)` 方法处理复选框的 `change` 事件,并使用 `taskCompleted.emit(taskId)` 发射事件。 * `AppComponent` 是父组件,它定义了任务列表数据 `tasks`,并在模板中使用 `` 标签引入 `TaskListComponent`。 * `[tasks]="tasks"` 将父组件的 `tasks` 数据绑定到子组件的 `tasks` 输入属性。 * `(taskCompleted)="onTaskCompleted($event)"` 监听子组件的 `taskCompleted` 输出事件,并调用 `onTaskCompleted` 方法处理。 **5.1.4 组件关系图 (mermaid graph TD):** ```mermaid graph TD A[AppComponent] --> B[TaskListComponent] A -- tasks (Input) --> B B -- taskCompleted (Output) --> A ``` **图表解释:** * `AppComponent` 是父组件,`TaskListComponent` 是子组件。 * 父组件通过 `tasks` 输入属性向子组件传递数据。 * 子组件通过 `taskCompleted` 输出属性向父组件传递事件。 **5.1.5 组件化实践总结** * 充分利用组件化思想,将应用拆分成小的、独立的组件。 * 合理划分组件职责,保持组件的单一性和可复用性。 * 使用输入属性 `@Input()` 和输出属性 `@Output()` 进行父子组件之间的通信。 * 善用组件生命周期钩子,在组件的不同阶段执行相应的逻辑。 * 编写清晰的组件文档和示例,方便团队成员理解和使用组件。 ### 5.2 模块化与懒加载:提升应用性能和可维护性 Angular 应用通常由多个模块组成,模块化可以帮助我们组织和管理应用代码,提高代码的可维护性和可扩展性。懒加载 (Lazy Loading) 是一种优化技术,它允许我们按需加载模块,减少应用的初始加载时间,提升用户体验。 **5.2.1 模块化的优势** * **代码组织 (Code Organization):** 模块将相关的功能代码组织在一起,使得应用结构更清晰,易于理解和维护。 * **命名空间 (Namespace):** 模块创建了独立的命名空间,避免了全局命名冲突。 * **封装 (Encapsulation):** 模块可以限制内部组件、服务和管道的可见性,提高代码的安全性。 * **懒加载 (Lazy Loading):** 模块可以配置为懒加载,按需加载,减少应用的初始加载时间。 **5.2.2 模块的结构** 一个 Angular 模块通常包含以下几个部分: * **声明 (Declarations):** 声明属于该模块的组件、指令和管道。 * **导入 (Imports):** 导入该模块依赖的其他模块,例如 `CommonModule`、`BrowserModule`、自定义模块等。 * **导出 (Exports):** 导出该模块的组件、指令和管道,使其可以在其他模块中使用。 * **提供者 (Providers):** 注册该模块提供的服务。 * **引导 (Bootstrap):** 指定启动组件 (通常只有根模块 `AppModule` 会配置)。 **5.2.3 代码实践:创建模块和懒加载** 我们创建一个 `FeatureModule` 模块,并配置为懒加载。 **feature.module.ts:** ```typescript import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FeatureComponent } from './feature.component'; import { FeatureRoutingModule } from './feature-routing.module'; @NgModule({ declarations: [ FeatureComponent ], imports: [ CommonModule, FeatureRoutingModule ] }) export class FeatureModule { } ``` **feature.component.ts:** ```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-feature', template: `

Feature Module Content

This is a lazy-loaded feature module.

` }) export class FeatureComponent { } ``` **feature-routing.module.ts:** ```typescript import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { FeatureComponent } from './feature.component'; const routes: Routes = [ { path: '', component: FeatureComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class FeatureRoutingModule { } ``` **在 `AppRoutingModule` 中配置懒加载:** **app-routing.module.ts:** ```typescript import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AppComponent } from './app.component'; const routes: Routes = [ { path: '', component: AppComponent }, { path: 'feature', loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule) } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } ``` **代码详解:** * `FeatureModule` 是一个特性模块,包含 `FeatureComponent` 和路由配置。 * `FeatureRoutingModule` 定义了 `FeatureModule` 的路由,使用 `RouterModule.forChild(routes)` 创建子路由模块。 * `AppRoutingModule` 是根路由模块,使用 `RouterModule.forRoot(routes)` 创建根路由模块。 * 在 `AppRoutingModule` 的路由配置中,`path: 'feature'` 配置了访问路径为 `feature` 的路由。 * `loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)` 配置了懒加载,当访问 `/feature` 路径时,才会异步加载 `FeatureModule`。 **5.2.4 模块关系图 (mermaid graph TD):** ```mermaid graph TD A[AppModule] --> B[FeatureModule] A -- imports --> B (Lazy Loaded) C[AppRoutingModule] --> D[FeatureRoutingModule] C -- loadChildren --> D ``` **图表解释:** * `AppModule` 是根模块,`FeatureModule` 是特性模块。 * `FeatureModule` 被配置为懒加载,只有在需要时才会被加载。 * `AppRoutingModule` 和 `FeatureRoutingModule` 分别是根路由模块和特性路由模块。 * `AppRoutingModule` 通过 `loadChildren` 配置懒加载 `FeatureRoutingModule`。 **5.2.5 模块化与懒加载实践总结** * 将应用按照功能模块划分为多个模块,例如特性模块、共享模块、核心模块等。 * 使用懒加载配置特性模块,减少应用的初始加载时间,提升用户体验。 * 导出共享模块中的组件、指令和管道,使其可以在其他模块中使用。 * 使用核心模块提供应用级别的服务和配置。 * 避免模块之间的循环依赖,保持模块的独立性和可维护性。 ### 5.3 响应式编程与 RxJS:处理异步操作和数据流 Angular 深度集成了响应式编程库 RxJS (Reactive Extensions for JavaScript),RxJS 提供了强大的工具来处理异步操作、事件流和数据流。理解和掌握 RxJS 对于构建复杂的 Angular 应用至关重要。 **5.3.1 响应式编程的优势** * **异步操作管理 (Asynchronous Operation Management):** RxJS 提供了优雅的方式来处理异步操作,例如 HTTP 请求、事件监听等,避免了回调地狱。 * **数据流处理 (Data Stream Processing):** RxJS 将数据视为流,可以对数据流进行各种操作,例如过滤、转换、合并等,简化了复杂的数据处理逻辑。 * **错误处理 (Error Handling):** RxJS 提供了统一的错误处理机制,可以集中处理异步操作中的错误。 * **代码简洁 (Code Conciseness):** 使用 RxJS 可以用更简洁的代码实现复杂的异步逻辑。 **5.3.2 RxJS 的核心概念** * **Observable (可观察对象):** 表示一个数据流,可以发出零个、一个或多个值,并在完成或发生错误时结束。 * **Observer (观察者):** 订阅 Observable 并接收其发出的值、完成通知和错误通知。 * **Operators (操作符):** 用于操作 Observable 发出的数据流,例如 `map`、`filter`、`mergeMap`、`catchError` 等。 * **Subscription (订阅):** 表示 Observable 和 Observer 之间的连接,可以通过 `unsubscribe()` 方法取消订阅。 **5.3.3 代码实践:使用 RxJS 处理 HTTP 请求** 我们使用 Angular 的 `HttpClient` 服务和 RxJS 来发起 HTTP GET 请求,并处理响应数据和错误。 **task.service.ts:** ```typescript import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; interface Task { id: number; name: string; completed: boolean; } @Injectable({ providedIn: 'root' }) export class TaskService { private apiUrl = 'api/tasks'; // 模拟 API 端点 constructor(private http: HttpClient) { } getTasks(): Observable { return this.http.get(this.apiUrl) .pipe( map(tasks => tasks.map(task => ({ ...task, name: task.name.toUpperCase() }))), // 转换数据 catchError(this.handleError('getTasks', [])) // 错误处理 ); } private handleError(operation = 'operation', result?: T) { return (error: any): Observable => { console.error(`${operation} failed: ${error.message}`); return of(result as T); // 返回空结果,保持应用运行 }; } } ``` **task-list.component.ts (修改):** ```typescript import { Component, OnInit } from '@angular/core'; import { TaskService } from './task.service'; interface Task { id: number; name: string; completed: boolean; } @Component({ selector: 'app-task-list', templateUrl: './task-list.component.html', styleUrls: ['./task-list.component.css'] }) export class TaskListComponent implements OnInit { tasks: Task[] = []; constructor(private taskService: TaskService) { } ngOnInit(): void { this.getTasks(); } getTasks(): void { this.taskService.getTasks().subscribe( tasks => this.tasks = tasks, error => console.error('Error fetching tasks:', error) ); } } ``` **代码详解:** * `TaskService` 使用 `HttpClient` 发起 HTTP GET 请求到 `/api/tasks` 端点。 * `getTasks()` 方法返回 `Observable`,表示一个任务列表的数据流。 * `pipe()` 方法用于链式调用 RxJS 操作符。 * `map()` 操作符将服务器返回的任务列表数据进行转换,将任务名称转换为大写。 * `catchError()` 操作符用于捕获 HTTP 请求错误,并使用 `handleError()` 方法处理错误。 * `handleError()` 方法记录错误信息到控制台,并返回一个空的 `Observable` 数组,保证应用不会崩溃。 * `TaskListComponent` 在 `ngOnInit()` 生命周期钩子中调用 `getTasks()` 方法。 * `subscribe()` 方法订阅 `taskService.getTasks()` 返回的 `Observable`,当数据到达时,更新 `tasks` 属性。 * `subscribe()` 方法的第二个参数用于处理错误,将错误信息记录到控制台。 **5.3.4 RxJS 操作符流程图 (mermaid graph TD):** ```mermaid graph TD A[HttpClient.get()] --> B(Observable); B -- pipe(map()) --> C{map Operator}; C -- map data --> D(Observable - Mapped); D -- pipe(catchError()) --> E{catchError Operator}; E -- handle error / return empty --> F(Observable - Error Handled); F --> G[TaskService.getTasks() Return]; ``` **图表解释:** * `HttpClient.get()` 方法返回一个 `Observable`。 * `map` 操作符对 `Observable` 发出的数据进行转换。 * `catchError` 操作符处理 `Observable` 中的错误。 * 最终 `TaskService.getTasks()` 方法返回经过操作符处理的 `Observable`。 **5.3.5 响应式编程与 RxJS 实践总结** * 学习和掌握 RxJS 的核心概念,例如 Observable、Observer、Operators 和 Subscription。 * 使用 RxJS 处理异步操作和事件流,避免回调地狱。 * 善用 RxJS 操作符,简化数据处理逻辑,提高代码可读性。 * 使用 `catchError` 操作符处理错误,保证应用的健壮性。 * 在组件销毁时取消订阅 Observable,避免内存泄漏。 ### 5.4 状态管理策略:构建可预测和可维护的应用状态 在大型 Angular 应用中,状态管理变得尤为重要。有效的状态管理策略可以帮助我们组织和管理应用状态,提高应用的可预测性、可维护性和可测试性。 **5.4.1 状态管理的重要性** * **数据共享 (Data Sharing):** 状态管理可以方便地在应用的不同组件之间共享数据。 * **状态追踪 (State Tracking):** 状态管理可以追踪应用状态的变化,方便调试和维护。 * **可预测性 (Predictability):** 良好的状态管理策略可以使应用状态的变化可预测,减少 bug 的产生。 * **可维护性 (Maintainability):** 集中管理应用状态可以提高代码的可维护性和可扩展性。 **5.4.2 Angular 中的状态管理方案** * **组件状态 (Component State):** 组件自身维护的状态,适用于组件内部的局部状态。 * **服务状态 (Service State):** 使用 Angular 服务来维护应用级别的状态,适用于简单的全局状态管理。 * **状态管理库 (State Management Libraries):** 使用专门的状态管理库,例如 NgRx、Akita、NgXs 等,适用于大型复杂应用的状态管理。 **5.4.3 代码实践:使用服务进行状态管理** 我们使用 Angular 服务来管理任务列表的状态,并演示如何在组件之间共享状态。 **task-state.service.ts:** ```typescript import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; interface Task { id: number; name: string; completed: boolean; } @Injectable({ providedIn: 'root' }) export class TaskStateService { private tasksSubject = new BehaviorSubject([]); tasks$ = this.tasksSubject.asObservable(); // 可观察的任务列表 constructor() { // 模拟初始任务数据 this.setTasks([ { id: 1, name: '初始任务 1', completed: false }, { id: 2, name: '初始任务 2', completed: true } ]); } getTasks(): Task[] { return this.tasksSubject.value; } setTasks(tasks: Task[]): void { this.tasksSubject.next(tasks); } addTask(task: Task): void { const currentTasks = this.getTasks(); this.setTasks([...currentTasks, task]); } toggleComplete(taskId: number): void { const currentTasks = this.getTasks(); const updatedTasks = currentTasks.map(task => { if (task.id === taskId) { return { ...task, completed: !task.completed }; } return task; }); this.setTasks(updatedTasks); } } ``` **task-list.component.ts (修改):** ```typescript import { Component, OnInit, OnDestroy } from '@angular/core'; import { TaskStateService } from './task-state.service'; import { Subscription } from 'rxjs'; interface Task { id: number; name: string; completed: boolean; } @Component({ selector: 'app-task-list', templateUrl: './task-list.component.html', styleUrls: ['./task-list.component.css'] }) export class TaskListComponent implements OnInit, OnDestroy { tasks: Task[] = []; tasksSubscription?: Subscription; constructor(private taskStateService: TaskStateService) { } ngOnInit(): void { this.tasksSubscription = this.taskStateService.tasks$.subscribe(tasks => this.tasks = tasks); } ngOnDestroy(): void { if (this.tasksSubscription) { this.tasksSubscription.unsubscribe(); } } toggleComplete(taskId: number): void { this.taskStateService.toggleComplete(taskId); } } ``` **task-input.component.ts (新增):** ```typescript import { Component } from '@angular/core'; import { TaskStateService } from './task-state.service'; interface Task { id: number; name: string; completed: boolean; } @Component({ selector: 'app-task-input', template: ` 添加任务 ` }) export class TaskInputComponent { constructor(private taskStateService: TaskStateService) { } addTask(taskName: string): void { if (taskName.trim()) { const newTask: Task = { id: Date.now(), // 简单生成 ID name: taskName, completed: false }; this.taskStateService.addTask(newTask); } } } ``` **app.component.html (修改):** ```html

我的任务列表

``` **代码详解:** * `TaskStateService` 使用 `BehaviorSubject` 来维护任务列表状态。 * `tasks$` 是一个 `Observable`,组件可以订阅它来获取最新的任务列表数据。 * `getTasks()`, `setTasks()`, `addTask()`, `toggleComplete()` 方法用于操作任务列表状态。 * `TaskListComponent` 订阅 `taskStateService.tasks$`,当任务列表状态变化时,组件会自动更新。 * `TaskInputComponent` 使用 `taskStateService.addTask()` 方法添加新的任务。 * 两个组件都通过 `TaskStateService` 共享和更新任务列表状态。 **5.4.4 服务状态管理流程图 (mermaid graph TD):** ```mermaid graph TD A[TaskStateService] -- tasks$ (Observable) --> B[TaskListComponent]; A -- addTask() / toggleComplete() --> A; C[TaskInputComponent] -- addTask() --> A; B -- toggleComplete() --> A; ``` **图表解释:** * `TaskStateService` 是状态管理的中心,维护任务列表状态。 * `TaskListComponent` 和 `TaskInputComponent` 都与 `TaskStateService` 交互。 * `TaskListComponent` 订阅 `tasks$` 获取状态更新。 * `TaskInputComponent` 和 `TaskListComponent` 通过调用 `TaskStateService` 的方法来更新状态。 **5.4.5 状态管理实践总结** * 根据应用规模和复杂程度选择合适的状态管理方案。 * 对于简单的应用,可以使用组件状态或服务状态进行管理。 * 对于大型复杂应用,可以考虑使用状态管理库,例如 NgRx、Akita、NgXs 等。 * 将状态管理逻辑集中到服务或状态管理库中,保持组件的纯粹性和可测试性。 * 使用 Observable 和响应式编程模式来管理状态变化。 * 遵循单向数据流原则,使状态变化可预测和易于追踪。 ### 5.5 全面测试:保障应用质量和长期维护性 测试是软件开发过程中至关重要的一环,对于 Angular 应用来说,全面的测试可以保障应用质量、提高代码可靠性、降低 bug 数量,并为长期维护提供保障。 **5.5.1 测试的重要性** * **缺陷预防 (Defect Prevention):** 测试可以在开发早期发现和修复 bug,减少 bug 进入生产环境的风险。 * **代码质量保障 (Code Quality Assurance):** 测试可以验证代码的正确性和功能完整性,提高代码质量。 * **回归测试 (Regression Testing):** 测试可以确保代码修改不会引入新的 bug,保证代码的稳定性。 * **代码文档 (Code Documentation):** 测试用例可以作为代码的文档,帮助理解代码的功能和行为。 * **长期维护性 (Long-term Maintainability):** 完善的测试体系可以降低代码维护成本,提高应用的长期维护性。 **5.5.2 Angular 应用的测试类型** * **单元测试 (Unit Testing):** 测试应用中最小的可测试单元,例如组件、服务、管道等,通常使用 Jest 或 Karma + Jasmine 进行单元测试。 * **集成测试 (Integration Testing):** 测试组件之间的交互,模块之间的集成,以及与外部系统 (例如 API) 的集成,可以使用 Angular Testing Library 或 Cypress 进行集成测试。 * **端到端测试 (End-to-End Testing - E2E Testing):** 模拟用户在浏览器中的真实操作,测试应用的完整流程和用户体验,可以使用 Cypress 或 Protractor (Angular 官方 E2E 测试工具,但已弃用,推荐 Cypress) 进行 E2E 测试。 **5.5.3 代码实践:编写单元测试** 我们为 `TaskListComponent` 组件编写单元测试用例。 **task-list.component.spec.ts:** ```typescript import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TaskListComponent } from './task-list.component'; import { By } from '@angular/platform-browser'; describe('TaskListComponent', () => { let component: TaskListComponent; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ declarations: [TaskListComponent] }).compileComponents(); fixture = TestBed.createComponent(TaskListComponent); component = fixture.componentInstance; component.tasks = [ { id: 1, name: 'Task 1', completed: false }, { id: 2, name: 'Task 2', completed: true } ]; fixture.detectChanges(); // 触发数据绑定和变更检测 }); it('should create', () => { expect(component).toBeTruthy(); }); it('should display tasks correctly', () => { const taskElements = fixture.debugElement.queryAll(By.css('li')); expect(taskElements.length).toBe(2); expect(taskElements[0].nativeElement.textContent).toContain('Task 1'); expect(taskElements[1].nativeElement.textContent).toContain('Task 2'); }); it('should emit taskCompleted event when checkbox is clicked', () => { spyOn(component.taskCompleted, 'emit'); // 监听输出事件 const checkbox = fixture.debugElement.query(By.css('input[type="checkbox"]')).nativeElement; checkbox.click(); expect(component.taskCompleted.emit).toHaveBeenCalledWith(1); // 验证事件是否被发射 }); it('should apply "completed" class when task is completed', () => { const completedSpan = fixture.debugElement.queryAll(By.css('.completed')); expect(completedSpan.length).toBe(1); // 只有一个任务完成,应该有一个 .completed 元素 expect(completedSpan[0].nativeElement.textContent).toContain('Task 2'); }); }); ``` **代码详解:** * `describe('TaskListComponent', ...)` 定义测试套件,用于组织 `TaskListComponent` 组件的测试用例。 * `beforeEach(() => { ... })` 在每个测试用例执行之前执行,用于初始化测试环境。 * `TestBed.configureTestingModule({ declarations: [TaskListComponent] }).compileComponents();` 配置测试模块,声明需要测试的组件。 * `fixture = TestBed.createComponent(TaskListComponent);` 创建组件 fixture,用于访问组件实例和 DOM 元素。 * `component = fixture.componentInstance;` 获取组件实例。 * `component.tasks = [ ... ];` 设置组件的输入属性。 * `fixture.detectChanges();` 触发数据绑定和变更检测,更新 DOM 元素。 * `it('should create', ...)` 定义一个测试用例,验证组件是否成功创建。 * `expect(component).toBeTruthy();` 断言组件实例存在。 * `fixture.debugElement.queryAll(By.css('li'));` 使用 `By.css` 选择器查询 DOM 元素。 * `spyOn(component.taskCompleted, 'emit');` 使用 `spyOn` 监听组件的输出事件 `taskCompleted.emit`。 * `checkbox.click();` 模拟用户点击复选框操作。 * `expect(component.taskCompleted.emit).toHaveBeenCalledWith(1);` 断言输出事件 `emit` 方法被调用,并传递了正确的参数。

发布者: 作者: 转发
评论区 (0)
U