跳到主要内容

指令系统

指令是 Angular 中用于扩展 HTML 元素行为的特殊类。Angular 提供了三种类型的指令:组件、属性指令和结构指令。本章将详细介绍指令的创建和使用。

指令类型

类型说明示例
组件带有模板的指令@Component
属性指令改变元素外观或行为ngClassngStyle
结构指令改变 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'
});
}
}

小结

  1. 属性指令改变元素的外观或行为,如 ngClassngStyle
  2. 结构指令改变 DOM 结构,如 *ngIf*ngFor
  3. 创建指令使用 @Directive 装饰器,通过 ElementRefRenderer2 操作 DOM
  4. 指令组合可以将多个指令的功能合并到一个指令中
  5. ng-containerng-template 是常用的模板工具

下一步

掌握了指令系统后,接下来学习 表单处理,了解如何处理用户输入。