本章目标:深入理解Angular组件的核心概念,掌握组件的创建、使用和通信方式,构建可复用的UI组件。
什么是Angular组件?
组件是Angular应用的基本构建块,负责控制屏幕的一部分(视图)。每个组件包含:
TypeScript类
包含组件的业务逻辑和数据
HTML模板
定义组件的视图结构
样式文件
定义组件的外观样式
装饰器
提供组件的元数据
组件树结构示例
AppComponent (根组件)
HeaderComponent
MainContentComponent
FooterComponent
UserListComponent
ProductComponent
创建组件的两种方式
方式1:使用Angular CLI(推荐)
使用Angular CLI可以快速生成标准化的组件文件:
CLI命令
# 完整命令
ng generate component user-profile
# 简写
ng g c user-profile
# 带路径
ng g c admin/user-list
# 内联模板(不生成HTML文件)
ng g c login --inline-template
# 内联样式(不生成CSS文件)
ng g c login --inline-style
# 不生成测试文件
ng g c user --skip-tests
生成的文件结构
src/app/user-profile/
├── user-profile.component.ts
├── user-profile.component.html
├── user-profile.component.css
└── user-profile.component.spec.ts
方式2:手动创建组件
了解组件的每个部分,手动创建可以帮助深入理解:
user-profile.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent {
// 组件属性
userName = '张三';
userAge = 25;
isActive = true;
// 组件方法
getGreeting(): string {
return `你好,${this.userName}!`;
}
toggleStatus(): void {
this.isActive = !this.isActive;
}
}
user-profile.component.html
<div class="user-card">
<h2>{{ userName }}的档案</h2>
<p>{{ getGreeting() }}</p>
<div class="user-info">
<p>年龄: {{ userAge }}</p>
<p>状态:
<span [class.active]="isActive"
[class.inactive]="!isActive">
{{ isActive ? '活跃' : '不活跃' }}
</span>
</p>
</div>
<button (click)="toggleStatus()">
切换状态
</button>
</div>
组件装饰器详解
@Component装饰器为组件提供元数据配置:
| 元数据属性 | 描述 | 示例 |
|---|---|---|
| selector | 定义组件在HTML中的标签名 | selector: 'app-user' |
| templateUrl | 指定外部模板文件路径 | templateUrl: './user.component.html' |
| template | 内联模板(直接写在TS文件中) | template: <h1>{{title}}</h1> |
| styleUrls | 外部样式文件路径(数组) | styleUrls: ['./user.component.css'] |
styles | 内联样式(直接写在TS文件中) | styles: ['h1 { color: red; }'] |
| changeDetection | 变更检测策略 | changeDetection: ChangeDetectionStrategy.OnPush |
组件数据绑定
Angular提供了多种数据绑定方式:
数据绑定示例
属性绑定
<!-- 单向:数据源 -> 视图 -->
<img [src]="imageUrl">
<div [class.active]="isActive"></div>
事件绑定
<!-- 单向:视图 -> 数据源 -->
<button (click)="save()">保存</button>
<input (input)="onInput($event)">
双向绑定
<!-- 双向:视图 <-> 数据源 -->
<input [(ngModel)]="userName">
<app-child [(value)]="parentValue"></app-child>
组件生命周期钩子
Angular组件有一系列生命周期钩子,允许我们在特定阶段执行代码:
ngOnChanges
当输入属性发生变化时调用
变更检测
ngOnInit
组件初始化时调用(只执行一次)
创建阶段
ngDoCheck
每个变更检测周期调用
变更检测
ngAfterContentInit
内容投影初始化后调用
创建阶段
ngAfterContentChecked
每次检查内容投影后调用
变更检测
ngAfterViewInit
组件视图初始化后调用
创建阶段
ngAfterViewChecked
每次检查组件视图后调用
变更检测
ngOnDestroy
组件销毁前调用
销毁阶段
实现生命周期钩子
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `<h1>生命周期演示</h1>`
})
export class LifecycleDemoComponent
implements OnInit, OnDestroy {
ngOnInit(): void {
console.log('组件初始化完成');
// 在这里执行初始化逻辑
// 例如:加载数据、订阅事件
}
ngOnDestroy(): void {
console.log('组件即将销毁');
// 在这里执行清理逻辑
// 例如:取消订阅、清理定时器
}
}
最佳实践建议
- ngOnInit:用于初始化数据,避免在构造函数中执行复杂逻辑
- ngOnDestroy:必须清理订阅和定时器,防止内存泄漏
- ngOnChanges:监控输入属性的变化,执行相应操作
- ngAfterViewInit:访问DOM元素或子组件
- 避免在ngDoCheck中执行复杂操作,会影响性能
组件通信
组件之间的通信是Angular应用的关键部分:
父组件
通过属性传递数据
@Input()
子组件
通过事件发送数据
@Output()
父组件 → 子组件(@Input)
// parent.component.ts
@Component({
template: `
<app-child
[userData]="currentUser"
[showDetails]="true">
</app-child>
`
})
export class ParentComponent {
currentUser = { name: '张三', age: 25 };
}
// child.component.ts
@Component({...})
export class ChildComponent {
@Input() userData: any;
@Input() showDetails: boolean = false;
}
子组件 → 父组件(@Output)
// child.component.ts
@Component({...})
export class ChildComponent {
@Output() userUpdated = new EventEmitter<any>();
updateUser(): void {
this.userUpdated.emit({
name: '李四',
age: 30
});
}
}
// parent.component.ts
@Component({
template: `
<app-child
(userUpdated)="onUserUpdated($event)">
</app-child>
`
})
export class ParentComponent {
onUserUpdated(user: any): void {
console.log('用户已更新:', user);
}
}
通过服务进行组件通信
对于非父子关系的组件,可以使用服务进行通信:
// data.service.ts - 共享数据服务
@Injectable({ providedIn: 'root' })
export class DataService {
private dataSubject = new BehaviorSubject<string>('');
data$ = this.dataSubject.asObservable();
updateData(newData: string): void {
this.dataSubject.next(newData);
}
}
// component-a.ts - 发送数据
@Component({...})
export class ComponentA {
constructor(private dataService: DataService) {}
sendData(): void {
this.dataService.updateData('Hello from A');
}
}
// component-b.ts - 接收数据
@Component({...})
export class ComponentB {
receivedData: string = '';
constructor(private dataService: DataService) {
this.dataService.data$.subscribe(data => {
this.receivedData = data;
});
}
}
组件最佳实践
组件设计原则
- 单一职责:每个组件只做一件事
- 可复用性:设计通用的、可配置的组件
- 松耦合:减少组件间的直接依赖
- 高内聚:相关功能放在同一个组件
- 容器组件:管理数据逻辑
- 展示组件:只负责UI展示
常见错误
- 在构造函数中执行复杂逻辑
- 忘记在
ngOnDestroy中清理订阅 - 在模板中使用复杂的表达式
- 组件过大,职责过多
- 过度使用双向绑定
- 在组件中直接操作DOM
完整示例:用户卡片组件
创建一个完整的、可复用的用户卡片组件:
// user-card.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.css']
})
export class UserCardComponent {
@Input() user: User = {
id: 0,
name: '',
email: '',
avatar: '',
isActive: false
};
@Input() showActions: boolean = true;
@Output() selected = new EventEmitter<number>();
@Output() deleted = new EventEmitter<number>();
onSelect(): void {
this.selected.emit(this.user.id);
}
onDelete(): void {
this.deleted.emit(this.user.id);
}
getStatusText(): string {
return this.user.isActive ? '活跃' : '离线';
}
getStatusClass(): string {
return this.user.isActive ? 'status-active' : 'status-inactive';
}
}
interface User {
id: number;
name: string;
email: string;
avatar: string;
isActive: boolean;
}
<!-- user-card.component.html -->
<div class="user-card" [class.card-active]="user.isActive">
<div class="card-header">
<img [src]="user.avatar" [alt]="user.name" class="avatar">
<div class="user-info">
<h3>{{ user.name }}</h3>
<p class="email">{{ user.email }}</p>
</div>
<span class="status" [class]="getStatusClass()">
{{ getStatusText() }}
</span>
</div>
<div class="card-body">
<p>用户ID: {{ user.id }}</p>
</div>
<div class="card-footer" *ngIf="showActions">
<button class="btn btn-primary" (click)="onSelect()">
选择用户
</button>
<button class="btn btn-danger" (click)="onDelete()">
删除
</button>
</div>
</div>
本章总结
通过本章学习,你应该掌握了:
- Angular组件的结构和创建方式
- 组件装饰器的配置和元数据
- 数据绑定的三种方式
- 完整的组件生命周期钩子
- 父子组件之间的通信机制
- 组件设计的最佳实践