Angular 作为前端主流的企业级框架,指令(Directive)是其核心特性之一,而结构型指令更是日常开发中高频使用的工具 —— 它们直接塑造 DOM 结构,决定元素的 “生死” 与 “循环”。其中*ngIf和*ngFor作为最基础也最核心的内置结构指令,掌握其使用技巧能大幅提升开发效率,避免常见坑点。本文将从基础用法到进阶技巧,带你吃透这两个指令。
一、先搞懂:什么是结构型指令?
结构型指令的核心作用是修改 DOM 结构,比如添加、删除、替换元素,或重复渲染一组元素。Angular 内置的结构指令都以*开头(语法糖),本质是对<ng-template>的简化封装。
需要注意:一个元素上不能同时使用多个结构型指令(比如不能同时写*ngIf和*ngFor),这是 Angular 的核心规则,避免指令执行顺序冲突。
二、*ngIf:条件渲染的正确姿势
*ngIf用于根据条件决定是否渲染元素(而非隐藏),这是它与[hidden]的核心区别 ——*ngIf不满足条件时,元素会从 DOM 中完全移除,而[hidden]只是通过 CSS 隐藏,仍存在于 DOM 中。
1. 基础用法:简单条件判断
最常用的场景是根据布尔值渲染元素:
<!-- 基础写法 --> <div *ngIf="isShow">满足条件,我会显示</div> <!-- 反向条件 --> <div *ngIf="!isUserLogin">未登录,请先登录</div>对应的组件类:
@Component({ selector: 'app-ngIf-demo', templateUrl: './ngIf-demo.component.html' }) export class NgIfDemoComponent { isShow = true; isUserLogin = false; }2. 进阶:else 分支与<ng-template>
当需要 “满足条件显示 A,不满足显示 B” 时,可结合else和<ng-template>实现:
<!-- 带else的写法 --> <div *ngIf="hasPermission; else noPermission"> 您有访问权限,可操作 </div> <!-- ng-template定义else模板 --> <ng-template #noPermission> <div>无访问权限,请联系管理员</div> </ng-template>这里#noPermission是模板引用变量,else后直接引用即可。
3. 高级:then-else 多分支
如果需要 “满足条件显示 A,否则显示 B,且可动态切换”,可使用then语法:
<div *ngIf="status; then successTpl else errorTpl"></div> <ng-template #successTpl> <div>操作成功 ✅</div> </ng-template> <ng-template #errorTpl> <div>操作失败 ❌</div> </ng-template>这种写法更灵活,适合多状态切换的场景。
4. 避坑:*ngIf 与 null/undefined 的处理
*ngIf会将0、''、null、undefined、false都判定为 “假”,但实际开发中需注意:
<!-- 错误示例:若list为[],仍会显示(因为[]是真) --> <div *ngIf="list">列表有数据</div> <!-- 正确示例:判断数组是否有内容 --> <div *ngIf="list && list.length > 0">列表有数据</div>5. 小技巧:利用 * ngIf 提升性能
对于复杂组件(比如包含大量逻辑的表单、图表),用*ngIf替代[hidden],可避免组件初始化和变更检测,提升页面性能:
<!-- 差:组件仍会初始化,只是隐藏 --> <app-complex-chart [hidden]="!showChart"></app-complex-chart> <!-- 优:不满足条件时,组件完全不渲染 --> <app-complex-chart *ngIf="showChart"></app-complex-chart>三、*ngFor:循环渲染的核心技巧
*ngFor用于遍历数组 / 可迭代对象,重复渲染元素,是列表渲染的核心指令。掌握其进阶用法,能解决大部分循环场景的问题。
1. 基础用法:遍历数组
<!-- 基础遍历 --> <ul> <li *ngFor="let item of userList"> {{ item.name }} - {{ item.age }}岁 </li> </ul>组件类:
export class NgForDemoComponent { userList = [ { name: '张三', age: 25 }, { name: '李四', age: 30 }, { name: '王五', age: 28 } ]; }2. 必加项:trackBy 提升性能
默认情况下,*ngFor会通过对象引用判断元素是否变化,当数组重新赋值(比如接口刷新)时,即使数据内容不变,Angular 也会销毁并重建所有 DOM 元素,性能极低。
解决方案:使用trackBy指定唯一标识:
<!-- 带trackBy的遍历 --> <ul> <li *ngFor="let item of userList; trackBy: trackByUserId"> {{ item.name }} - {{ item.age }}岁 </li> </ul>组件类中定义trackBy方法:
// 以id为唯一标识 trackByUserId(index: number, item: any): number { return item.id; // 确保item有唯一的id字段 }trackBy接收两个参数:index(索引)和item(当前项),返回值必须是唯一的(如 id、手机号等)。
3. 常用变量:index、even、odd、first、last
*ngFor提供了内置变量,满足特殊场景的需求:
| 变量名 | 含义 |
|---|---|
| index | 当前项的索引(从 0 开始) |
| even | 是否为偶数项(布尔值) |
| odd | 是否为奇数项(布尔值) |
| first | 是否为第一项(布尔值) |
| last | 是否为最后一项(布尔值) |
示例:
<table> <tr *ngFor="let item of userList; trackBy: trackByUserId; let i = index; let isEven = even; let isFirst = first; let isLast = last" [style.backgroundColor]="isEven ? '#f5f5f5' : '#fff'" > <td>{{ i + 1 }}</td> <!-- 索引从1开始 --> <td>{{ item.name }}</td> <td>{{ isFirst ? '✅ 第一项' : isLast ? '🔚 最后一项' : '' }}</td> </tr> </table>4. 嵌套 * ngFor:多维数组遍历
处理二维数组(比如表格的行和列)时,可嵌套*ngFor:
<!-- 嵌套循环:遍历行和列 --> <table border="1"> <tr *ngFor="let row of tableData; trackBy: trackByRowIndex"> <td *ngFor="let cell of row; trackBy: trackByCellIndex"> {{ cell }} </td> </tr> </table>组件类:
tableData = [ ['姓名', '年龄', '性别'], ['张三', 25, '男'], ['李四', 30, '女'] ]; trackByRowIndex(index: number): number { return index; } trackByCellIndex(index: number): number { return index; }5. 避坑:ngFor 与ngIf 的冲突解决
前面提到 “一个元素不能同时用多个结构指令”,如果需要 “循环且条件过滤”,有两种解决方案:
方案 1:外层套<ng-container>(推荐)
<ng-container>是 Angular 的 “空容器”,不会生成实际 DOM,专门用于包裹指令:
<ul> <ng-container *ngFor="let item of userList; trackBy: trackByUserId"> <!-- 只显示年龄大于25的用户 --> <li *ngIf="item.age > 25"> {{ item.name }} - {{ item.age }}岁 </li> </ng-container> </ul>方案 2:提前过滤数组(性能更优)
在组件类中先过滤数据,模板中直接遍历过滤后的数组:
// 组件类 get filteredUserList() { return this.userList.filter(item => item.age > 25); }<!-- 模板 --> <ul> <li *ngFor="let item of filteredUserList; trackBy: trackByUserId"> {{ item.name }} - {{ item.age }}岁 </li> </ul>这种方式减少了模板中的逻辑,且避免了循环内的条件判断,性能更好。
四、总结:核心要点
*ngIf是 “DOM 级别的条件渲染”,而非隐藏,适合控制元素的存在与否,优先于[hidden];*ngIf的else/then分支需结合<ng-template>使用,满足多状态切换;*ngFor必须加trackBy,避免数组更新时 DOM 全量重建,提升性能;- 利用
*ngFor的内置变量(index/even/odd 等)解决特殊场景; - 避免在同一元素上混用
*ngIf和*ngFor,优先用<ng-container>或提前过滤数组。
掌握这两个指令的核心技巧,能解决 Angular 开发中 80% 的 DOM 结构控制场景。后续还可以探索自定义结构指令,进一步扩展 Angular 的 DOM 操作能力。