组件基础
组件是 Angular 应用的核心构建块。每个 Angular 应用都是一棵组件树,根组件连接整个应用。本章将深入介绍组件的概念、创建和使用方式。
什么是组件?
组件是一个带有 @Component 装饰器的 TypeScript 类,它定义了:
- 模板(Template):组件的视图,使用 HTML 编写
- 样式(Styles):组件的外观,使用 CSS/SCSS 编写
- 逻辑(Logic):组件的行为,使用 TypeScript 编写
组件的基本结构
import { Component } from '@angular/core';
@Component({
selector: 'app-hello', // CSS 选择器,用于在模板中使用
template: `<h1>Hello Angular!</h1>`, // 内联模板
styles: [`h1 { color: blue; }`] // 内联样式
})
export class HelloComponent {
// 组件逻辑
}
创建组件
使用 CLI 创建
ng generate component user-profile
# 或简写
ng g c user-profile
CLI 会自动创建以下文件:
src/app/user-profile/
├── user-profile.component.ts # 组件逻辑
├── user-profile.component.html # 组件模板
├── user-profile.component.scss # 组件样式
└── user-profile.component.spec.ts # 测试文件
手动创建组件
import { Component } from '@angular/core';
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrl: './user-card.component.scss'
})
export class UserCardComponent {
name = '张三';
age = 25;
}
组件元数据
@Component 装饰器接受一个配置对象,常用属性如下:
| 属性 | 说明 |
|---|---|
selector | CSS 选择器,定义组件在 HTML 中的使用方式 |
template | 内联模板字符串 |
templateUrl | 外部模板文件路径 |
styles | 内联样式数组 |
styleUrl | 外部样式文件路径 |
standalone | 是否为独立组件(默认 true) |
imports | 导入的模块、组件、指令、管道 |
schemas | 允许的非 Angular 元素 |
选择器类型
组件支持多种选择器类型:
// 元素选择器(最常用)
@Component({
selector: 'app-user-card', // 使用:<app-user-card></app-user-card>
})
// 属性选择器
@Component({
selector: '[appHighlight]', // 使用:<div appHighlight></div>
})
// 类选择器
@Component({
selector: '.app-button', // 使用:<button class="app-button"></button>
})
独立组件
从 Angular 14 开始,推荐使用独立组件(Standalone Component)。独立组件不需要 NgModule,可以直接导入和使用其他组件、指令和管道。
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserAvatarComponent } from './user-avatar.component';
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [CommonModule, UserAvatarComponent],
template: `
<app-user-avatar [user]="user"></app-user-avatar>
<div *ngIf="isLoggedIn">
<p>欢迎,{{ user.name }}!</p>
</div>
`
})
export class UserProfileComponent {
user = { name: '张三', avatar: 'avatar.jpg' };
isLoggedIn = true;
}
独立组件的优势
- 简化架构:不再需要 NgModule,减少样板代码
- 更好的 Tree-shaking:未使用的组件不会被包含在最终包中
- 更直观的依赖管理:组件明确声明自己的依赖
- 更易于迁移:可以逐步迁移现有应用
模板定义
内联模板
适用于简单组件,模板直接写在组件文件中:
@Component({
selector: 'app-counter',
template: `
<p>计数器: {{ count }}</p>
<button (click)="increment()">增加</button>
`
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
}
}
外部模板
适用于复杂组件,模板放在单独的 HTML 文件中:
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html'
})
export class UserListComponent {
users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
}
user-list.component.html:
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
样式定义
内联样式
@Component({
selector: 'app-alert',
template: `<div class="alert">警告信息</div>`,
styles: [`
.alert {
padding: 10px;
background-color: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
}
`]
})
外部样式文件
@Component({
selector: 'app-alert',
template: `<div class="alert">警告信息</div>`,
styleUrl: './alert.component.scss'
})
样式作用域
Angular 默认使用 视图封装(View Encapsulation),组件样式只作用于该组件的模板:
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-example',
template: `<p class="text">这段文字的样式不会影响其他组件</p>`,
styles: [`.text { color: red; }`],
encapsulation: ViewEncapsulation.Emulated // 默认值
})
封装模式说明:
| 模式 | 说明 |
|---|---|
Emulated | 默认模式,样式只作用于当前组件 |
None | 样式全局生效,会影响其他组件 |
ShadowDom | 使用浏览器原生 Shadow DOM |
组件通信
输入属性(@Input)
父组件向子组件传递数据:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ name }}</h3>
<p>年龄: {{ age }}</p>
</div>
`
})
export class UserCardComponent {
@Input() name = '';
@Input({ required: true }) age!: number; // 必需输入
}
父组件使用:
@Component({
selector: 'app-parent',
template: `
<app-user-card [name]="'张三'" [age]="25"></app-user-card>
`,
imports: [UserCardComponent]
})
export class ParentComponent {}
输出属性(@Output)
子组件向父组件发送事件:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>计数: {{ count }}</p>
<button (click)="onIncrement()">增加</button>
`
})
export class CounterComponent {
count = 0;
@Output() countChange = new EventEmitter<number>();
onIncrement() {
this.count++;
this.countChange.emit(this.count);
}
}
父组件监听事件:
@Component({
selector: 'app-parent',
template: `
<app-counter (countChange)="onCountChange($event)"></app-counter>
`,
imports: [CounterComponent]
})
export class ParentComponent {
onCountChange(count: number) {
console.log('计数变化:', count);
}
}
双向绑定
结合 @Input 和 @Output 实现双向绑定:
@Component({
selector: 'app-input',
template: `
<input [value]="value" (input)="onInput($event)">
`
})
export class InputComponent {
@Input() value = '';
@Output() valueChange = new EventEmitter<string>();
onInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
this.valueChange.emit(value);
}
}
使用 [(...)] 语法:
<app-input [(value)]="name"></app-input>
生命周期钩子
Angular 组件有完整的生命周期,通过实现特定接口可以响应各个阶段:
import { Component, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lifecycle',
template: `<p>生命周期示例</p>`
})
export class LifecycleComponent implements OnInit, OnChanges, OnDestroy {
ngOnChanges(changes: SimpleChanges) {
console.log('输入属性变化', changes);
}
ngOnInit() {
console.log('组件初始化完成');
}
ngOnDestroy() {
console.log('组件即将销毁');
}
}
生命周期顺序
| 钩子 | 说明 | 调用时机 |
|---|---|---|
ngOnChanges | 输入属性变化时 | 多次调用 |
ngOnInit | 组件初始化 | 一次 |
ngDoCheck | 自定义变更检测 | 多次调用 |
ngAfterContentInit | 内容投影完成 | 一次 |
ngAfterContentChecked | 内容投影检测后 | 多次调用 |
ngAfterViewInit | 视图初始化完成 | 一次 |
ngAfterViewChecked | 视图检测后 | 多次调用 |
ngOnDestroy | 组件销毁前 | 一次 |
小结
- 组件是 Angular 应用的核心,由模板、样式和逻辑组成
- 独立组件是现代 Angular 推荐的方式,简化了架构
- @Input 和 @Output 实现组件间的数据传递
- 生命周期钩子允许在组件生命周期的特定时刻执行代码
下一步
了解了组件基础后,接下来学习 模板语法,掌握数据绑定和事件处理的详细用法。