本章目标:掌握Angular指令的核心概念,熟练使用内置指令,学会创建自定义指令,理解指令的生命周期和最佳实践。
三种指令类型
*
结构型指令
修改DOM结构
[]
属性型指令
修改元素外观行为
@
组件指令
带模板的指令
什么是Angular指令?
指令是Angular的核心特性之一,用于扩展HTML的功能。它们是在DOM元素上添加行为的类。Angular有三种类型的指令:
| 指令类型 | 语法 | 作用 | 示例 |
|---|---|---|---|
| 组件指令 | @Component |
带模板的指令,控制视图区域 | <app-user></app-user> |
| 结构型指令 | *前缀 |
通过添加/删除DOM元素来改变布局 | *ngIf, *ngFor, *ngSwitch |
| 属性型指令 | []或不带* |
改变元素、组件或其他指令的外观或行为 | ngClass, ngStyle, ngModel |
1. 结构型指令
结构型指令通过添加或删除DOM元素来改变DOM的布局。它们都以*(星号)为前缀。
*ngIf - 条件显示
根据条件添加或移除DOM元素。
<!-- 基本用法 -->
<div *ngIf="isLoggedIn">
欢迎回来!
</div>
<!-- 配合else使用 -->
<div *ngIf="user; else noUser">
欢迎, {{ user.name }}!
</div>
<ng-template #noUser>
请先登录
</ng-template>
<!-- 配合then/else -->
<div *ngIf="isLoading; then loading; else content"></div>
<ng-template #loading>
<div class="spinner">加载中...</div>
</ng-template>
<ng-template #content>
<div>内容区域</div>
</ng-template>
组件代码
export class AppComponent {
isLoggedIn = true;
user = { name: '张三' };
isLoading = false;
toggleLogin(): void {
this.isLoggedIn = !this.isLoggedIn;
}
}
*ngFor - 循环渲染
基于数组循环渲染DOM元素。
<!-- 基本用法 -->
<ul>
<li *ngFor="let item of items">
{{ item }}
</li>
</ul>
<!-- 获取索引和其他变量 -->
<div *ngFor="let user of users;
let i = index;
let isFirst = first;
let isLast = last;
let isEven = even;
let isOdd = odd">
{{ i + 1 }}. {{ user.name }}
</div>
<!-- 配合trackBy提高性能 -->
<div *ngFor="let item of items; trackBy: trackById">
{{ item.name }}
</div>
组件代码
export class AppComponent {
items = ['Angular', 'React', 'Vue'];
users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
];
trackById(index: number, item: any): number {
return item.id;
}
}
*ngSwitch - 条件切换
基于不同值显示不同的内容。
<!-- 基本用法 -->
<div [ngSwitch]="status">
<div *ngSwitchCase="'success'">操作成功</div>
<div *ngSwitchCase="'error'">操作失败</div>
<div *ngSwitchCase="'loading'">加载中...</div>
<div *ngSwitchDefault>未知状态</div>
</div>
<!-- 配合枚举使用 -->
<div [ngSwitch]="user.role">
<div *ngSwitchCase="Role.Admin">管理员</div>
<div *ngSwitchCase="Role.User">普通用户</div>
<div *ngSwitchCase="Role.Guest">访客</div>
</div>
组件代码
export class AppComponent {
status = 'success';
user = { role: 'admin' };
enum Role {
Admin = 'admin',
User = 'user',
Guest = 'guest'
}
}
结构型指令实时演示
2. 属性型指令
属性型指令改变元素、组件或其他指令的外观或行为。它们通常作为元素的属性使用。
ngClass - 动态CSS类
动态添加或移除CSS类。
<!-- 根据条件添加类 -->
<div [ngClass]="{ 'active': isActive,
'error': hasError,
'disabled': isDisabled }">
内容
</div>
<!-- 使用字符串数组 -->
<div [ngClass]="['card', 'highlight', theme]">
卡片内容
</div>
<!-- 使用组件方法 -->
<button [ngClass]="getButtonClasses()">
点击我
</button>
<!-- 配合对象语法 -->
<div [ngClass]="classObject"></div>
组件代码
export class AppComponent {
isActive = true;
hasError = false;
isDisabled = false;
theme = 'dark-theme';
getButtonClasses(): any {
return {
'btn': true,
'btn-primary': this.isActive,
'btn-disabled': this.isDisabled
};
}
classObject = {
'highlight': true,
'rounded': true,
'shadow': false
};
}
ngStyle - 动态样式
动态设置元素的样式。
<!-- 根据条件设置样式 -->
<div [ngStyle]="{ 'color': textColor,
'font-size': fontSize + 'px',
'background-color': bgColor }">
内容
</div>
<!-- 使用组件属性 -->
<div [ngStyle]="customStyles">
自定义样式
</div>
<!-- 计算样式 -->
<div [ngStyle]="{
'width': width + 'px',
'height': height + 'px',
'opacity': isVisible ? 1 : 0.5
}">
动态元素
</div>
<!-- 单个样式绑定 -->
<div [style.width]="containerWidth"
[style.height.px]="100">
固定高度
</div>
组件代码
export class AppComponent {
textColor = 'red';
fontSize = 16;
bgColor = '#f0f0f0';
width = 200;
height = 100;
isVisible = true;
customStyles = {
'padding': '20px',
'margin': '10px',
'border': '1px solid #ccc',
'border-radius': '8px'
};
}
ngModel - 双向绑定
在表单元素和组件属性之间建立双向绑定。
<!-- 基本用法 -->
<input [(ngModel)]="userName"
placeholder="请输入姓名">
<!-- 配合事件监听 -->
<input [(ngModel)]="email"
(ngModelChange)="onEmailChange($event)">
<!-- 下拉选择框 -->
<select [(ngModel)]="selectedOption">
<option *ngFor="let option of options"
[value]="option.value">
{{ option.label }}
</option>
</select>
<!-- 复选框 -->
<input type="checkbox"
[(ngModel)]="isAgreed"> 我同意
<!-- 单选按钮组 -->
<input type="radio"
[(ngModel)]="gender"
value="male"> 男
<input type="radio"
[(ngModel)]="gender"
value="female"> 女
组件代码
export class AppComponent {
userName = '';
email = '';
selectedOption = '';
isAgreed = false;
gender = 'male';
options = [
{ value: 'angular', label: 'Angular' },
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' }
];
onEmailChange(newEmail: string): void {
console.log('邮箱已更新:', newEmail);
}
}
属性型指令实时演示
3. 创建自定义指令
Angular允许你创建自己的指令来封装可重用的行为。
创建自定义指令步骤
1
使用CLI生成指令
ng generate directive highlight
# 或简写
ng g d highlight
2
配置指令元数据
使用@Directive装饰器指定选择器
3
注入依赖项
在构造函数中注入需要的服务或元素
4
实现指令逻辑
使用生命周期钩子实现指令行为
5
在模块中声明
确保指令在模块的declarations数组中
自定义属性型指令
// highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input() appHighlight = '';
@Input() defaultColor = '';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || this.defaultColor || 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
@HostListener('click') onClick() {
console.log('元素被点击');
}
private highlight(color: string): void {
this.el.nativeElement.style.backgroundColor = color;
}
// 监听输入属性变化
ngOnChanges(): void {
console.log('高亮颜色已更新:', this.appHighlight);
}
}
<!-- 使用自定义指令 -->
<p [appHighlight]="'lightblue'">
鼠标悬停我会高亮
</p>
<p [appHighlight]="hoverColor" defaultColor="lightgreen">
自定义高亮
</p>
<p appHighlight>
使用默认颜色
</p>
自定义结构型指令
// unless.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
private hasView = false;
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
// 显示内容
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
// 隐藏内容
this.viewContainer.clear();
this.hasView = false;
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
}
<!-- 使用自定义结构型指令 -->
<div *appUnless="isHidden">
当isHidden为false时显示
</div>
<!-- 相当于ngIf的反向操作 -->
<div *ngIf="!isHidden">
相同效果
</div>
指令生命周期钩子
自定义指令可以使用的生命周期钩子:
import { Directive, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
@Directive({
selector: '[appLifecycleDemo]'
})
export class LifecycleDemoDirective implements OnInit, OnDestroy, OnChanges {
// 输入属性变化时调用
ngOnChanges(changes: SimpleChanges): void {
console.log('输入属性变化:', changes);
}
// 指令初始化时调用
ngOnInit(): void {
console.log('指令初始化');
// 在这里执行初始化逻辑
}
// 指令销毁时调用
ngOnDestroy(): void {
console.log('指令销毁');
// 在这里执行清理逻辑
}
}
4. 指令高级特性
深入学习指令的高级用法和特性。
输入/输出属性
指令可以定义输入和输出属性。
// 支持多个输入别名
@Directive({ selector: '[appAdvanced]' })
export class AdvancedDirective {
@Input('appAdvanced') mainValue: string = '';
@Input() config: any;
@Input() disabled: boolean = false;
@Output() valueChange = new EventEmitter<string>();
@Output() activated = new EventEmitter<boolean>();
private internalValue: string = '';
@Input()
set value(val: string) {
this.internalValue = val;
this.valueChange.emit(val);
}
get value(): string {
return this.internalValue;
}
}
宿主事件监听
使用@HostListener监听宿主元素事件。
@Directive({ selector: '[appEventTracker]' })
export class EventTrackerDirective {
@HostListener('click', ['$event'])
onClick(event: MouseEvent): void {
console.log('点击事件:', event);
event.preventDefault();
}
@HostListener('mouseenter')
onMouseEnter(): void {
console.log('鼠标进入');
}
@HostListener('mouseleave')
onMouseLeave(): void {
console.log('鼠标离开');
}
@HostListener('window:resize', ['$event'])
onWindowResize(event: Event): void {
console.log('窗口大小改变:', event);
}
@HostListener('document:keydown.escape', ['$event'])
onEscapeKey(event: KeyboardEvent): void {
console.log('Escape键被按下');
}
}
宿主属性绑定
使用@HostBinding绑定宿主元素的属性。
@Directive({ selector: '[appStyleManager]' })
export class StyleManagerDirective {
@HostBinding('class.active')
isActive: boolean = false;
@HostBinding('style.backgroundColor')
bgColor: string = 'transparent';
@HostBinding('attr.role')
role: string = 'button';
@HostBinding('attr.aria-disabled')
get ariaDisabled(): string {
return this.isActive ? 'false' : 'true';
}
@HostBinding('style.width.px')
width: number = 100;
@HostBinding('class')
get elementClasses(): string {
return this.isActive ? 'btn btn-primary' : 'btn btn-secondary';
}
}
导出指令别名
可以在模板中给指令起别名:
<!-- 使用指令别名 -->
<div appHighlight #highlighter="appHighlight">
高亮内容
</div>
<button (click)="highlighter.onMouseEnter()">
手动触发高亮
</button>
<!-- 在指令中启用导出 -->
// highlight.directive.ts
@Directive({
selector: '[appHighlight]',
exportAs: 'appHighlight' // 添加这一行
})
指令最佳实践
推荐做法
- 为指令选择有意义的、描述性的名称
- 使用
ng generate directive创建指令 - 在
ngOnDestroy中清理订阅和事件监听器 - 使用
@HostBinding和@HostListener简化代码 - 为指令提供适当的默认值
- 确保指令在模块中正确声明
- 使用
exportAs允许模板引用指令实例 - 保持指令单一职责,功能集中
应避免的做法
- 避免在指令中直接操作DOM(使用ElementRef时小心)
- 不要在指令构造函数中执行复杂逻辑
- 避免创建过于复杂、功能过多的指令
- 不要忘记在
ngOnDestroy中清理资源 - 避免指令之间的循环依赖
- 不要在指令中硬编码样式值
- 避免在多个地方重复相同的指令逻辑
- 不要过度使用指令,简单的属性绑定可能更合适
常用指令速查表
| 指令 | 类型 | 作用 | 示例 |
|---|---|---|---|
*ngIf |
结构型 | 条件显示/隐藏元素 | *ngIf="isVisible" |
*ngFor |
结构型 | 循环渲染元素 | *ngFor="let item of items" |
[ngSwitch] |
结构型 | 多条件分支显示 | [ngSwitch]="value" |
[ngClass] |
属性型 | 动态CSS类绑定 | [ngClass]="{'active': isActive}" |
[ngStyle] |
属性型 | 动态样式绑定 | [ngStyle]="{'color': textColor}" |
[(ngModel)] |
属性型 | 表单双向绑定 | [(ngModel)]="userName" |
[ngTemplateOutlet] |
结构型 | 动态插入模板 | [ngTemplateOutlet]="templateRef" |
[ngPlural] |
属性型 | 复数形式显示 | [ngPlural]="count" |
本章总结
通过本章学习,你应该掌握了:
- 结构型指令:使用
*ngIf、*ngFor、*ngSwitch控制DOM结构 - 属性型指令:使用
ngClass、ngStyle、ngModel修改元素外观行为 - 自定义指令:创建自己的属性型和结构型指令
- 指令生命周期:理解指令的初始化和销毁过程
- 高级特性:使用
@HostListener、@HostBinding等高级功能 - 最佳实践:编写高效、可维护的指令代码