跳到主要内容

组件基础

组件是 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 装饰器接受一个配置对象,常用属性如下:

属性说明
selectorCSS 选择器,定义组件在 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;
}

独立组件的优势

  1. 简化架构:不再需要 NgModule,减少样板代码
  2. 更好的 Tree-shaking:未使用的组件不会被包含在最终包中
  3. 更直观的依赖管理:组件明确声明自己的依赖
  4. 更易于迁移:可以逐步迁移现有应用

模板定义

内联模板

适用于简单组件,模板直接写在组件文件中:

@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组件销毁前一次

小结

  1. 组件是 Angular 应用的核心,由模板、样式和逻辑组成
  2. 独立组件是现代 Angular 推荐的方式,简化了架构
  3. @Input@Output 实现组件间的数据传递
  4. 生命周期钩子允许在组件生命周期的特定时刻执行代码

下一步

了解了组件基础后,接下来学习 模板语法,掌握数据绑定和事件处理的详细用法。