Skip to content

5 Directive 指令

指令是Angular提供的操作DOM的途径. 指令分为属性指令和结构指令

Structural Directives (结构指令): 增加, 删除DOM节点以修改布局, 使用*作为指令前缀

Attribute Directives (属性指令): 修改现有元素的外观或行为, 使用[]包裹

5.1 Build-in Directives

5.1.1 *ngIf Structural Directives

根据条件渲染DOM节点或移除DOM节点

<div *ngIf="data.length == 0">没有更多数据</div>
<div *ngIf="data.length > 0; then dataList else noData"></div>
<ng-template #dataList>课程列表</ng-template>
<ng-template #noData>没有更多数据</ng-template>

app.component.ts

import { Component } from '@angular/core';
import { Form, FormControl, FormGroup, NgForm } from '@angular/forms';

interface List {
  id: number
  name: string
  age: number
}
// 组件类 被@Component装饰器装饰
@Component({

  // 制定组件的使用方式, 当前问标记形式
  // app-root => <app-root></app-root> 

  selector: 'app-root', // 当前组件调用的时候你要以什么形式去调用
  templateUrl: './app.component.html', // 当前组件对应的模版是什么 组件模版文件路径
  styleUrls: ['./app.component.css'], // 当前组件对应的样式文件
})

// 导出一个类 
export class AppComponent {
  list: List[] = [
    {
      id: 1,
      name: 'sam',
      age: 20
    },
    {
      id: 2,
      name: 'jo',
      age: 30
    }
  ]
}

app.component.html:

<div *ngIf="list.length === 0 ">no content</div>

Screenshot 2025-01-26 at 15.07.32

Screenshot 2025-01-26 at 15.07.40

Screenshot 2025-01-26 at 15.07.55

Screenshot 2025-01-26 at 15.08.34

Screenshot 2025-01-26 at 15.08.23

app.component.html:

<div *ngIf="list.length === 0; then noData"></div>
<ng-template #noData>
    <div>no content</div>
</ng-template>
<!-- #这个是模板引用变量,可以在模板中引用这个变量 -->
<!-- ng-template是一个模板容器,可以用来包裹一段html代码,然后通过ngIf来控制这段html代码的显示与隐藏 -->
<!-- *ngIf="list.length === 0; then noData" 这个语法是ngIf的语法糖,它的意思是当list.length === 0时,显示noData这个模板
    也就是说当list为空时,显示noData这个模板,否则不显示 

    如果<div> 在ng-template 里, 则会显示<div>no content</div>,否则会显示no content
-->

Screenshot 2025-01-26 at 15.23.15

Screenshot 2025-01-26 at 15.23.42

Screenshot 2025-01-26 at 15.24.16

Screenshot 2025-01-26 at 15.24.23

Screenshot 2025-01-26 at 15.24.42

Screenshot 2025-01-26 at 15.24.47

5.1.2 [hidden] Attribute Directives

根据条件显示 DOM 节点或隐藏 DOM 节点(display)

<divhidden]="data.length == 0">课程列表</div>
<divhidden]="data.length > 0">没有更多数据</div>

Screenshot 2025-01-26 at 15.37.37

Screenshot 2025-01-26 at 15.37.47

Screenshot 2025-01-26 at 15.38.34

Screenshot 2025-01-26 at 15.37.59

5.1.3 *ngFor

遍历数据生成HTML结构

interface List {
  id: number
  name: string
  age: number
}

list: List[] = [
  {
    id: 1,
    name: 'sam',
    age: 20
  },
  {
    id: 2,
    name: 'jo',
    age: 30
  }
]

app.component.ts

import { Component } from '@angular/core';
import { Form, FormControl, FormGroup, NgForm } from '@angular/forms';

interface List {
  id: number
  name: string
  age: number
}
// 组件类 被@Component装饰器装饰
@Component({

  // 制定组件的使用方式, 当前问标记形式
  // app-root => <app-root></app-root> 

  selector: 'app-root', // 当前组件调用的时候你要以什么形式去调用
  templateUrl: './app.component.html', // 当前组件对应的模版是什么 组件模版文件路径
  styleUrls: ['./app.component.css'], // 当前组件对应的样式文件
})

// 导出一个类 
export class AppComponent {
  list: List[] = [
    {
      id: 1,
      name: 'sam',
      age: 20
    },
    {
      id: 2,
      name: 'jo',
      age: 30
    }
  ]
}



// selector: '.app-root', // 当前组件调用的时候你要以什么形式去调用 

// app-root => <div class="app-root"></div>
// selector: '[app-root]', // 当前组件调用的时候你要以什么形式去调用
// app-root => <div app-root></div>

app.component.html

<div *ngIf="list.length === 0; then noData; else listData"></div>
<ng-template #noData>
    <div>no content</div>
</ng-template>
<ng-template #listData>
    <ul>
        <li *ngFor="let item of list ">
            <p>
                {{ item.id }}
                {{ item.name }}
                {{ item.age }}
            </p>
        </li>
    </ul>
</ng-template>

Screenshot 2025-01-26 at 15.51.54

Screenshot 2025-01-26 at 15.52.01

app.component.ts

import { Component } from '@angular/core';
import { Form, FormControl, FormGroup, NgForm } from '@angular/forms';

interface List {
  id: number
  name: string
  age: number
}
// 组件类 被@Component装饰器装饰
@Component({

  // 制定组件的使用方式, 当前问标记形式
  // app-root => <app-root></app-root> 

  selector: 'app-root', // 当前组件调用的时候你要以什么形式去调用
  templateUrl: './app.component.html', // 当前组件对应的模版是什么 组件模版文件路径
  styleUrls: ['./app.component.css'], // 当前组件对应的样式文件
})

// 导出一个类 
export class AppComponent {
  list: List[] = [
    {
      id: 1,
      name: 'sam',
      age: 20
    },
    {
      id: 2,
      name: 'jo',
      age: 30
    }
  ]

  identity(index: number, item: List) {
    console.log(index);
    console.log(item);
    return item.id;
  }
}



// selector: '.app-root', // 当前组件调用的时候你要以什么形式去调用 

// app-root => <div class="app-root"></div>
// selector: '[app-root]', // 当前组件调用的时候你要以什么形式去调用
// app-root => <div app-root></div>

app.component.html:

<div *ngIf="list.length === 0; then noData; else listData"></div>
<ng-template #noData>
    <div>no content</div>
</ng-template>
<ng-template #listData>
    <ul>
        <li *ngFor="
            let item of list; 
            let i = index; 
            let isFirst = first; 
            let isLast = last; 
            let isEven = even; 
            let isOdd = odd;
            trackBy: identity;
            " [ngClass]="{ even: isEven, odd: isOdd }">
            <p>
                {{ item.id }}
                {{ item.name }}
                {{ item.age }}
                index {{ i }}
                isFirst {{ isFirst }}
                isLast {{ isLast }}

            </p>
        </li>
    </ul>
</ng-template>
<!-- ngClass是angular提供的一个指令,用于动态添加或删除class -->
<!-- [ngClass]="{ even: isEven, odd: isOdd }" 是说如果isEven为true,则添加even这个class,如果isOdd为true,则添加odd这个class -->
<!-- .even {
    background: pink;
}

.odd {
    background: lightblue;
}

是说如果isEven为true,则添加even这个class,如果isOdd为true,则添加odd这个class 
Angular 会自动帮我们添加这个class到对应的元素上 -->

app.component.css:

.even {
    background: pink;
}

.odd {
    background: lightblue;
}

Screenshot 2025-01-26 at 16.18.50

Screenshot 2025-01-26 at 16.18.59

Screenshot 2025-01-26 at 16.19.10

5.2 Custom Directives

需求: 为元素设置默认背景颜色, 鼠标移入时的背景颜色以及移出时的背景颜色.

<div [appHover]="{ bgColor: 'skyblue' }">Hello Angular</div>

ng g d directives/hover

Screenshot 2025-01-17 at 15.21.51

Screenshot 2025-01-17 at 15.25.44

Screenshot 2025-01-17 at 15.25.56

import { AfterViewInit, Directive, ElementRef } from '@angular/core';
import { After } from 'v8';

// @Directive是个装饰器, 用来告诉Angular这是一个指令
@Directive({
  selector: '[appHover]'
})
export class HoverDirective implements AfterViewInit {

  constructor(private elementRef: ElementRef) { 
    //elementRef是一个指向宿主元素的引用
    //ElementRef是一个包装器,它包装了一个原生的DOM元素,可以通过nativeElement属性访问它
    // console.log(elementRef);
  }

  ngAfterViewInit(){
    console.log(this.elementRef.nativeElement);
  }

}

Screenshot 2025-01-17 at 15.31.41

Screenshot 2025-01-17 at 15.32.04

import { AfterViewInit, Directive, ElementRef } from '@angular/core';
import { After } from 'v8';

// @Directive是个装饰器, 用来告诉Angular这是一个指令
@Directive({
  selector: '[appHover]'
})
export class HoverDirective implements AfterViewInit {
  element: Element

  constructor(private elementRef: ElementRef) { 
    //elementRef是一个指向宿主元素的引用
    //ElementRef是一个包装器,它包装了一个原生的DOM元素,可以通过nativeElement属性访问它
    // console.log(elementRef);
    this.element = elementRef.nativeElement;
  }

  ngAfterViewInit(){
    console.log(this.element);
  }

}

Screenshot 2025-01-17 at 15.47.19

Screenshot 2025-01-17 at 15.47.40

import { AfterViewInit, Directive, ElementRef } from '@angular/core';
import { After } from 'v8';

// @Directive是个装饰器, 用来告诉Angular这是一个指令
@Directive({
  selector: '[appHover]'
})
export class HoverDirective implements AfterViewInit {
  element: HTMLElement

  constructor(private elementRef: ElementRef) { 
    //elementRef是一个指向宿主元素的引用
    //ElementRef是一个包装器,它包装了一个原生的DOM元素,可以通过nativeElement属性访问它
    // console.log(elementRef);
    this.element = elementRef.nativeElement;
  }

  ngAfterViewInit(){
    this.element.style.backgroundColor = 'red';
  }

}

Screenshot 2025-01-17 at 15.48.53

Screenshot 2025-01-17 at 15.48.58

hover.directive.ts

import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
import { After } from 'v8';

interface Options{
  bgColor?: string; // ? 表示可选
}
// @Directive是个装饰器, 用来告诉Angular这是一个指令
@Directive({
  selector: '[appHover]'
})


export class HoverDirective implements AfterViewInit {
  @Input("appHover") appHover: Options = {}
  // @Input 是一个装饰器,用来告诉Angular这个属性是一个输入属性
  // Options是一个接口,用来定义输入属性的类型
  element: HTMLElement

  constructor(private elementRef: ElementRef) { 
    //elementRef是一个指向宿主元素的引用
    //ElementRef是一个包装器,它包装了一个原生的DOM元素,可以通过nativeElement属性访问它
    // console.log(elementRef);
    this.element = elementRef.nativeElement;
  }

  ngAfterViewInit(){
    this.element.style.backgroundColor = this.appHover.bgColor||'skyblue';
  }

}

Screenshot 2025-01-17 at 15.55.47

Screenshot 2025-01-17 at 15.56.21

<!-- <div [appHover]="{bgColor: 'red' }">Hello Angular </div> -->
<div [appHover]="{}">Hello Angular </div>

Screenshot 2025-01-17 at 15.57.49

hover.directive.ts:

import { AfterViewInit, Directive, ElementRef, HostListener, Input } from '@angular/core';
import { After } from 'v8';

interface Options{
  bgColor?: string; // ? 表示可选
}
// @Directive是个装饰器, 用来告诉Angular这是一个指令
@Directive({
  selector: '[appHover]'
})


export class HoverDirective implements AfterViewInit {
  @Input("appHover") appHover: Options = {}
  // @Input 是一个装饰器,用来告诉Angular这个属性是一个输入属性
  // Options是一个接口,用来定义输入属性的类型
  element: HTMLElement

  constructor(private elementRef: ElementRef) { 
    //elementRef是一个指向宿主元素的引用
    //ElementRef是一个包装器,它包装了一个原生的DOM元素,可以通过nativeElement属性访问它
    // console.log(elementRef);
    this.element = elementRef.nativeElement;
  }// 可以通过ElementRef指令拿到元素

  ngAfterViewInit(){
    this.element.style.backgroundColor = this.appHover.bgColor||'skyblue';
  }
  // 组件初始化完成后,才能获取到元素

  @HostListener('mouseenter') enter(){
    this.element.style.backgroundColor = "pink";
  }

  @HostListener('mouseleave') leave(){
    this.element.style.backgroundColor = 'blue'
  }

}

Screenshot 2025-01-17 at 17.05.07

Screenshot 2025-01-17 at 17.05.21

在 Angular 中,自定义指令是扩展和增强 DOM 元素行为的重要工具。你提到的 @Directive 装饰器和相关的概念(如 @InputElementRef 等)是创建和使用自定义指令的核心部分。以下是它们的含义及作用的详细解释。

自定义指令分类

Angular 提供两种类型的指令:

  1. 结构型指令(Structural Directive) - 改变 DOM 的结构,例如 *ngIf*ngFor。 - 用于动态添加、移除或替换元素。
  2. 属性型指令(Attribute Directive) - 改变元素的外观或行为,例如 ngClassngStyle。 - 不改变 DOM 结构,仅修改属性或样式。

创建自定义指令的关键工具

1. @Directive 装饰器

  • @Directive 是定义自定义指令的装饰器。
  • 用法类似 @Component,但它不包含模板或样式。

示例:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appHighlight]' // 使用时加 [] 表示属性型指令
})
export class HighlightDirective {
  constructor(private el: ElementRef, private renderer: Renderer2) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
  }
}
  • 作用:为目标元素添加黄色背景色。
  • 使用
<p appHighlight>这段文字会被高亮。</p>

2. @Input

  • @Input 是一个装饰器,用于从父组件向指令传递值。
  • 通常结合 setter 使用,可以拦截并基于传入的值执行操作。

示例:

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input() set appHighlight(color: string) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color || 'yellow');
  }

  constructor(private el: ElementRef, private renderer: Renderer2) {}
}
  • 作用:允许动态设置背景颜色。
  • 使用
<p [appHighlight]="'blue'">这段文字会被设置为蓝色背景。</p>
<p [appHighlight]="'green'">这段文字会被设置为绿色背景。</p>

3. ElementRef

  • ElementRef 是 Angular 提供的类,用于直接访问 DOM 元素。
  • 属性 nativeElement 包含该 DOM 元素的引用。

注意:直接使用 ElementRef 操作 DOM 存在安全风险(如 XSS 攻击)。通常建议搭配 Renderer2 使用。

示例:

constructor(private el: ElementRef) {
  this.el.nativeElement.style.color = 'red'; // 直接操作元素
}

4. Renderer2

  • Renderer2 是一种安全操作 DOM 的抽象层。
  • 它提供的方法可以安全地修改 DOM,而不用直接访问 nativeElement

示例:

constructor(private el: ElementRef, private renderer: Renderer2) {
  this.renderer.setStyle(this.el.nativeElement, 'color', 'blue'); // 安全地设置样式
}

5. ViewContainerRefTemplateRef

这些主要用于结构型指令,帮助动态操作 DOM 结构。

  • TemplateRef:表示一个嵌套模板,用于动态生成视图。
  • ViewContainerRef:表示视图容器,可以向其中添加或移除视图。

示例:创建一个自定义的 *appIf 指令

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appIf]'
})
export class AppIfDirective {
  @Input() set appIf(condition: boolean) {
    if (condition) {
      this.vcRef.createEmbeddedView(this.templateRef); // 添加视图
    } else {
      this.vcRef.clear(); // 移除视图
    }
  }

  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) {}
}
  • 使用
<p *appIf="true">这段文字会显示。</p>
<p *appIf="false">这段文字不会显示。</p>

总结

工具 作用
@Directive 定义自定义指令,用于扩展 DOM 元素的行为。
@Input 从父组件传递数据到指令中,常用于动态控制指令的行为。
ElementRef 直接访问 DOM 元素。通常搭配 Renderer2 使用。
Renderer2 提供安全操作 DOM 的方法,用于设置样式、属性等。
ViewContainerRef 表示视图容器,用于添加或移除动态视图,通常用于结构型指令。
TemplateRef 表示嵌套模板,配合 ViewContainerRef 使用,用于动态生成视图。