指令系统
指令是 Angular 中用于扩展 HTML 元素行为的特殊类。Angular 提供了三种类型的指令:组件、属性指令和结构指令。本章将详细介绍指令的创建和使用。
指令类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 组件 | 带有模板的指令 | @Component |
| 属性指令 | 改变元素外观或行为 | ngClass、ngStyle |
| 结构指令 | 改变 DOM 结构 | *ngIf、*ngFor |
内置属性指令
ngClass
动态添加或移除 CSS 类:
import { Component } from '@angular/core';
import { NgClass } from '@angular/common';
@Component({
selector: 'app-ngclass-demo',
template: `
<!-- 字符串方式 -->
<div [ngClass]="'active bold'">多个类名</div>
<!-- 对象方式 -->
<div [ngClass]="{ active: isActive, disabled: isDisabled }">
条件类名
</div>
<!-- 数组方式 -->
<div [ngClass]="['class1', 'class2']">类名数组</div>
<!-- 方法返回 -->
<div [ngClass]="getClasses()">动态计算类名</div>
`,
imports: [NgClass]
})
export class NgClassDemoComponent {
isActive = true;
isDisabled = false;
getClasses() {
return {
active: this.isActive,
'text-primary': true
};
}
}
ngStyle
动态设置内联样式:
import { Component } from '@angular/core';
import { NgStyle } from '@angular/common';
@Component({
selector: 'app-ngstyle-demo',
template: `
<!-- 对象方式 -->
<div [ngStyle]="{
'color': textColor,
'font-size': fontSize + 'px',
'background-color': bgColor
}">
动态样式
</div>
<!-- 方法返回 -->
<div [ngStyle]="getStyles()">计算样式</div>
`,
imports: [NgStyle]
})
export class NgStyleDemoComponent {
textColor = 'red';
fontSize = 16;
bgColor = '#f0f0f0';
getStyles() {
return {
'color': this.textColor,
'font-size': `${this.fontSize}px`
};
}
}
创建属性指令
基本属性指令
创建一个高亮指令,鼠标悬停时改变背景色:
import { Directive, ElementRef, HostListener, HostBinding, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
@Input() appHighlight = 'yellow'; // 默认高亮颜色
@Input() defaultColor = 'transparent';
// 使用 HostBinding 绑定宿主元素属性
@HostBinding('style.backgroundColor') backgroundColor = this.defaultColor;
constructor(private el: ElementRef) {}
// 使用 HostListener 监听宿主元素事件
@HostListener('mouseenter') onMouseEnter() {
this.backgroundColor = this.appHighlight;
}
@HostListener('mouseleave') onMouseLeave() {
this.backgroundColor = this.defaultColor;
}
}
使用指令:
import { Component } from '@angular/core';
import { HighlightDirective } from './highlight.directive';
@Component({
selector: 'app-highlight-demo',
template: `
<p appHighlight="lightblue" defaultColor="white">
鼠标悬停时高亮显示(浅蓝色)
</p>
<p appHighlight="lightgreen">
鼠标悬停时高亮显示(浅绿色)
</p>
`,
imports: [HighlightDirective]
})
export class HighlightDemoComponent {}
使用 ElementRef 和 Renderer2
ElementRef 直接访问 DOM 元素,Renderer2 提供安全的 DOM 操作:
import { Directive, ElementRef, Renderer2, Input, OnInit } from '@angular/core';
@Directive({
selector: '[appBetterHighlight]',
standalone: true
})
export class BetterHighlightDirective implements OnInit {
@Input() defaultColor = 'transparent';
@Input() highlightColor = 'blue';
constructor(
private el: ElementRef,
private renderer: Renderer2
) {}
ngOnInit() {
this.renderer.setStyle(
this.el.nativeElement,
'background-color',
this.defaultColor
);
}
@HostListener('mouseenter') onMouseEnter() {
this.renderer.setStyle(
this.el.nativeElement,
'background-color',
this.highlightColor
);
}
@HostListener('mouseleave') onMouseLeave() {
this.renderer.setStyle(
this.el.nativeElement,
'background-color',
this.defaultColor
);
}
}
内置结构指令
*ngIf
条件渲染元素:
import { Component } from '@angular/core';
import { NgIf } from '@angular/common';
@Component({
selector: 'app-ngif-demo',
template: `
<button (click)="show = !show">切换显示</button>
<!-- 基本用法 -->
<p *ngIf="show">这段文字根据条件显示</p>
<!-- 带 else -->
<div *ngIf="isLoggedIn; else notLoggedIn">
<p>欢迎回来!</p>
</div>
<ng-template #notLoggedIn>
<p>请先登录</p>
</ng-template>
<!-- 带 then/else -->
<div *ngIf="score >= 60; then pass; else fail"></div>
<ng-template #pass><p>及格</p></ng-template>
<ng-template #fail><p>不及格</p></ng-template>
`,
imports: [NgIf]
})
export class NgIfDemoComponent {
show = true;
isLoggedIn = false;
score = 75;
}
*ngFor
循环渲染元素:
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';
@Component({
selector: 'app-ngfor-demo',
template: `
<ul>
<li *ngFor="let item of items; let i = index; let first = first; let last = last; trackBy: trackById"
[class.first]="first" [class.last]="last">
{{ i + 1 }}. {{ item.name }} - ¥{{ item.price }}
<span *ngIf="first">(首个)</span>
<span *ngIf="last">(末个)</span>
</li>
</ul>
`,
imports: [NgFor]
})
export class NgForDemoComponent {
items = [
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 },
{ id: 3, name: '橙子', price: 4 }
];
// trackBy 提高性能,避免不必要的 DOM 重建
trackById(index: number, item: { id: number }) {
return item.id;
}
}
*ngSwitch
多条件切换:
import { Component } from '@angular/core';
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
@Component({
selector: 'app-ngswitch-demo',
template: `
<select [(ngModel)]="status">
<option value="loading">加载中</option>
<option value="success">成功</option>
<option value="error">错误</option>
</select>
<div [ngSwitch]="status">
<p *ngSwitchCase="'loading'">正在加载...</p>
<p *ngSwitchCase="'success'">操作成功!</p>
<p *ngSwitchCase="'error'">发生错误</p>
<p *ngSwitchDefault>未知状态</p>
</div>
`,
imports: [NgSwitch, NgSwitchCase, NgSwitchDefault, FormsModule]
})
export class NgSwitchDemoComponent {
status = 'loading';
}
创建结构指令
创建一个 *appUnless 指令,与 *ngIf 相反:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]',
standalone: true
})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
// 条件为 false 且视图未创建,创建视图
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
// 条件为 true 且视图已创建,清除视图
this.viewContainer.clear();
this.hasView = false;
}
}
}
使用指令:
import { Component } from '@angular/core';
import { UnlessDirective } from './unless.directive';
@Component({
selector: 'app-unless-demo',
template: `
<button (click)="show = !show">切换</button>
<p *appUnless="show">
当 show 为 false 时显示这段文字
</p>
`,
imports: [UnlessDirective]
})
export class UnlessDemoComponent {
show = true;
}
结构指令的工作原理
结构指令前的 * 是语法糖,Angular 会将其转换为 <ng-template>:
<!-- 写法一:语法糖 -->
<div *ngIf="show">内容</div>
<!-- 写法二:实际编译结果 -->
<ng-template [ngIf]="show">
<div>内容</div>
</ng-template>
<!-- 写法三:显式使用 ng-template -->
<ng-template [ngIf]="show">
<div>内容</div>
</ng-template>
指令组合 API
Angular 15+ 支持指令组合,可以将多个指令的功能组合到一个指令中:
import { Directive, HostBinding, HostListener } from '@angular/core';
// 基础指令:添加边框
@Directive({
selector: '[appBorder]',
standalone: true
})
export class BorderDirective {
@HostBinding('style.border') border = '1px solid #ccc';
}
// 基础指令:添加内边距
@Directive({
selector: '[appPadding]',
standalone: true
})
export class PaddingDirective {
@HostBinding('style.padding') padding = '10px';
}
// 基础指令:添加圆角
@Directive({
selector: '[appRounded]',
standalone: true
})
export class RoundedDirective {
@HostBinding('style.border-radius') borderRadius = '8px';
}
// 组合指令:卡片样式
@Directive({
selector: '[appCard]',
standalone: true,
hostDirectives: [
BorderDirective,
PaddingDirective,
RoundedDirective
]
})
export class CardDirective {}
使用组合指令:
@Component({
selector: 'app-card-demo',
template: `
<!-- 使用组合指令,自动获得边框、内边距和圆角 -->
<div appCard>
<h3>卡片标题</h3>
<p>卡片内容</p>
</div>
`,
imports: [CardDirective]
})
export class CardDemoComponent {}
ng-container 和 ng-template
ng-container
ng-container 是一个不会渲染到 DOM 的容器元素:
@Component({
selector: 'app-ngcontainer-demo',
template: `
<!-- 使用 ng-container 分组,不影响 DOM 结构 -->
<p>
这是一段文字
<ng-container *ngIf="showExtra">
<span>额外的</span>
<span>内容</span>
</ng-container>
继续的文字
</p>
<!-- 在表格中使用 -->
<table>
<tr>
<ng-container *ngFor="let col of columns">
<th>{{ col.header }}</th>
</ng-container>
</tr>
</table>
`
})
export class NgContainerDemoComponent {
showExtra = true;
columns = [
{ header: '姓名' },
{ header: '年龄' },
{ header: '城市' }
];
}
ng-template
ng-template 定义模板片段,默认不渲染:
import { Component, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-ngtemplate-demo',
template: `
<!-- 定义模板 -->
<ng-template #alertTemplate let-message="msg" let-type="type">
<div class="alert alert-{{ type }}">
{{ message }}
</div>
</ng-template>
<button (click)="showAlert()">显示警告</button>
<div #alertContainer></div>
`
})
export class NgTemplateDemoComponent {
@ViewChild('alertTemplate') alertTemplate!: TemplateRef<any>;
@ViewChild('alertContainer', { read: ViewContainerRef }) alertContainer!: ViewContainerRef;
constructor(private viewContainer: ViewContainerRef) {}
showAlert() {
// 动态创建模板视图
this.alertContainer.clear();
this.alertContainer.createEmbeddedView(this.alertTemplate, {
msg: '这是一条警告信息',
type: 'warning'
});
}
}
小结
- 属性指令改变元素的外观或行为,如
ngClass、ngStyle - 结构指令改变 DOM 结构,如
*ngIf、*ngFor - 创建指令使用
@Directive装饰器,通过ElementRef和Renderer2操作 DOM - 指令组合可以将多个指令的功能合并到一个指令中
- ng-container 和 ng-template 是常用的模板工具
下一步
掌握了指令系统后,接下来学习 表单处理,了解如何处理用户输入。