Angular指令使用指南

本章目标:掌握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结构
  • 属性型指令:使用ngClassngStylengModel修改元素外观行为
  • 自定义指令:创建自己的属性型和结构型指令
  • 指令生命周期:理解指令的初始化和销毁过程
  • 高级特性:使用@HostListener@HostBinding等高级功能
  • 最佳实践:编写高效、可维护的指令代码