[Angular 基础] - 自定义指令,深入学习 directive

[Angular 基础] - 自定义指令,深入学习 directive

这篇笔记的前置笔记为 [Angular 基础] - 指令(directives),对 Angular 的 directives 不是很了解的可以先过一下这篇笔记

后面也会拓展一下项目,所以感兴趣的也可以补一下文后对应的项目:

  • 第一个 Angular 项目 - 静态页面
  • 第一个 Angular 项目 - 动态页面

创建新 directive

directive 的创建方式和 component 类似,这里选择用指令生成:

❯ ng g d directives/test --skip-tests
CREATE src/app/directives/test.directive.ts (137 bytes)
UPDATE src/app/app.module.ts (757 bytes)

运行这个指令就会在 src/app/directives 下创建一个新的 directive:

在这里插入图片描述

目前项目结构如下,这里 V 和 VM 层暂时不用去管,directive 会一个个过一遍

⚠️:如果手动生成 directive,同样需要在 app.module.ts 中导入对应的 directive:

@NgModule({declarations: [AppComponent,BasicHighlightDirective,BetterHighlightDirective,],imports: [BrowserModule, FormsModule],providers: [],bootstrap: [AppComponent],
})
export class AppModule {}

实现一个 attribute directive

一个空白的 directive 结构如下:

import { Directive } from '@angular/core';@Directive({selector: '[appBasicHighlight]',
})
export class BasicHighlightDirective {constructor() {}
}

首先分析一下 directive 的结构,这里的 @Directive@Component 一样都是装饰器,不过这里的使用比较简单,只是放入了一个 selector

selector 中的内容为绑定对应的 HTML Template 中的 attribute,也就是说 HTML template:

  • ✅ 使用 appBasicHighlight,就能够绑定对应的 directive
  • ❌ 不使用 [appBasicHighlight] 去进行属性绑定

这时候修改 app 的 VM 层:

<p appBasicHighlight>Style me with basic directive</p>
`

简单的 attribute directive

directive 的构造函数是比较重要的,它总共会提供 3 个参数用来操控对应的 DOM

使用 ElementRef

这里的 ElementRef 是对当前绑定指令的 HTML 元素的引用值,这里也就是 p 标签中的内容

通过直接修改 ElementRef 也是一种可以直接修改 DOM 元素的方式,使用方法如下:

@Directive({selector: '[appBasicHighlight]',
})
export class BasicHighlightDirective implements OnInit {constructor(private elementRef: ElementRef) {}ngOnInit() {console.log(this.elementRef);this.elementRef.nativeElement.style.backgroundColor = 'pink';}
}

效果如下:

在这里插入图片描述

这里简述一下修改:

  • implements OnInit 这一段算是补充吧

    尽管说 TS 的 implements 执行力比较差,不过我看了下官方文档都有用,就稍微标准化一下好了

  • ngOnInit 中执行对于样式的修改

    本案例使用 ngOnInit 或者直接在构造函数里修改样式其实没有什么特别大的区别,不过对于其他的情况,例如 HTML 中的内容是动态生成的情况下,直接在构造函数里就会造成内容的缺失。

通过这种方式就能够创建一个 attribute directive 了,不过直接使用 elementRef 并不是一个推荐的做法,下面会创建另一个 attribute directive,并使用推荐的方式去修改属性

复杂一些的 attribute directive

这里新建一个 directive,并将其命名为 better-highlight.directive,同时在 app 的 V 层新建一个 p 标签,并添加对应的 attribute directive:

<p appBetterHighlight>Style me with better directive</p>
renderer
import { Directive } from '@angular/core';@Directive({selector: '[appBetterHighlight]',
})
export class BetterHighlightDirective {constructor(private renderer: Renderer2) {}
}

这里要修改样式的方法是通过这个 renderer 去实现的

renderer: Renderer2 是 Angular 对 DOM 操作的一个 service 封装,其主要的优点在于提供统一的 API 使得其在浏览器坏境、SSR 环境以及 web worker 中都会有同样的表现。另外它也会对一些 HTML 元素进行清理,这样可以更觉有效的防止 XSS 攻击。

对于同样修改样式,这里依旧在 ngOnInit 中实现,实现的方式是通过调用 setStyle 进行:

@Directive({selector: '[appBetterHighlight]',
})
export class BetterHighlightDirective implements OnInit {// @Inputconstructor(private elementRef: ElementRef, private renderer: Renderer2) {}ngOnInit() {this.renderer.setStyle(this.elementRef.nativeElement,'background-color','lightblue');}
}

显示效果如下:

在这里插入图片描述

@HostListener

@HostListener 是 Angular 提供的,对当前元素所提供的事件绑定的一个装饰器,其语法如下:

@HostListener('event_name', ['$event'])
methodName(event: EventType): void {}

其中 :

  • event_name 为事件名称,如 click, mouseenter

  • $event 为对应的事件

    事件前添加 $ 算是 Angular 约定俗成的一种规范了

  • methodName,如其名,函数名

  • EventType,事件类型,可以不传,TS 用来做规范的

这里用 mouseentermouseleave 为例,对背景颜色进行修改,修改后代码如下:

@Directive({selector: '[appBetterHighlight]',
})
export class BetterHighlightDirective implements OnInit {constructor(private elementRef: ElementRef, private renderer: Renderer2) {}ngOnInit() {this.renderer.setStyle(this.elementRef.nativeElement,'background-color','lightblue');}@HostListener('mouseenter', ['$event']) mouseover(eventData: MouseEvent) {console.log(eventData);this.renderer.setStyle(this.elementRef.nativeElement,'background-color','lightgreen');}@HostListener('mouseleave') mouseleave(eventData: Event) {console.log(eventData);this.renderer.setStyle(this.elementRef.nativeElement,'background-color','lightblue');}
}

效果如下:

在这里插入图片描述

这里主要注意的是这么几个点:

  • 如果不传递 ['$event'],那么函数也不会自动监听到对应的事件

    这也是为什么 mouseenter 的时候能抓到 $event,但是 mouseleave 的时候抓不到的原因

  • Event 只是类型检查

    不是说没用,相反,如果确定类型的话,那么 TS 将会提供更好的类型检查和 intelligence 提示

    但是如果要写比较 generic 的方案,可能还是直接用 Event 比较好

💡:我添加了 CSS transition,让背景色过渡的稍微自然点,不过这个不是什么重点

@HostBinding

这个时候看到组件内出现了很多的重复代码:

this.renderer.setStyle(this.elementRef.nativeElement,'background-color',`${color}`
);

可以看到,这里唯一产生变化的只有需要被修改的颜色。

要解决这个问题,可以使用 Angular 提供的 @HostBinding 装饰器,它的语法为:

@HostBinding('property') propertyName: Type = value;

其中:

  • property 为想要绑定的元素属性

    这个案例中就是 style.backgroundColor

  • propertyName 为变量名

  • Type 为类型

    本案例为 string,其他的案例可能会出现 number, boolean,如果是可选项的话也可以为 undefined

  • value

使用如下:

export class BetterHighlightDirective implements OnInit {@HostBinding('style.backgroundColor') backgroundColor: string = 'lightblue';constructor(private elementRef: ElementRef, private renderer: Renderer2) {}ngOnInit() {// 已经有了默认值,这下面的代码也可以注释掉了// this.backgroundColor = 'lightblue';}@HostListener('mouseenter', ['$event']) mouseover(eventData: MouseEvent) {console.log(eventData);this.backgroundColor = 'lightgreen';}@HostListener('mouseleave') mouseleave(eventData: Event) {console.log(eventData);this.backgroundColor = 'lightblue';}
}

最后展示的效果依旧是一样的

动态添加属性

这个时候可以注意到,现在唯一要修改的地方就是颜色,这个情况也可以使用变量去存储这个修改的颜色,同时前面可以添加 @Input,这样可以让父元素动态重写颜色:

export class BetterHighlightDirective implements OnInit {@Input() defaultColor: string = 'lightblue';@Input() highlightColor: string = 'lightgreen';@HostBinding('style.backgroundColor') backgroundColor: string =this.defaultColor;constructor(private elementRef: ElementRef, private renderer: Renderer2) {}@HostListener('mouseenter', ['$event']) mouseover(eventData: MouseEvent) {console.log(eventData);this.backgroundColor = this.highlightColor;}@HostListener('mouseleave') mouseleave(eventData: Event) {console.log(eventData);this.backgroundColor = this.defaultColor;}
}

在不重写默认颜色时,效果是一样的。

但是父元素也可以选择重写默认值:

<pappBetterHighlightclass="bg-transition"[defaultColor]="'lightyellow'"highlightColor="violet"
>Style me with better directive
</p>

效果如下:

在这里插入图片描述

这里又有两个点需要注意的:

  • highlightColor="violet"

    这是一个特殊的语法缩写,本质上还是一个 property binding,而不是 HTML 所有的原生属性

    我这里特地用了两种写法,只是为了添加一下 note。为了更好的阅读性和理解,还是推荐使用 [customPropertyName]="'value'" 的写法

  • 背景颜色默认为蓝色

    这就是前面提到的 ngOnInit 的作用,这个情况下 Angular 的组件需要经历一个初始化的状态,在这个初始化的状态,它会绑定对应的属性——包括来自外部的属性

    这一段代码里我特地把 ngOnInit 注释掉了,没有了这个初始化的状态,那么当前组件依旧接受默认值,也就是 lightblue,把 ngOnInit 加回去,并添加对应的修改:

    ngOnInit() {this.backgroundColor = this.defaultColor;
    }
    

    才能将默认的背景色重写为父元素传进来的 lightyellow

实现一个 structural directive

官方文档有一个实现了 unless 的 structural directive,也可以参考一下,我这里就写一个 loading spinner 了。

实现如下:

@Directive({selector: '[appLoading]',
})
export class LoadingDirective {private loadingSpinner: HTMLElement;constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef,private renderer: Renderer2,private el: ElementRef) {this.loadingSpinner = this.renderer.createElement('div');this.renderer.addClass(this.loadingSpinner, 'spinner');}@Input() set appLoading(isLoading: boolean) {this.viewContainer.clear();if (isLoading) {this.renderer.appendChild(this.el.nativeElement.parentElement,this.loadingSpinner);} else {this.renderer.removeChild(this.el.nativeElement.parentElement,this.loadingSpinner);this.viewContainer.createEmbeddedView(this.templateRef);}}
}

app 的 V 层:

<div *appLoading="isLoading">some random syntax showing only when loading is false
</div>

VM 层修改变量,以及 CSS 我就不贴代码了

效果如下:

在这里插入图片描述

下面进入实现的分析部分

TemplateRef

即绑定 structural directive 的元素,在这个情况下,就是:

<div *appLoading="isLoading">some random syntax showing only when loading is false
</div>

简单的理解就是,当满足特定条件时,这里需要渲染的内容

ViewContainerRef

ViewContainerRef 就是管理渲染内容的容器

Angular 没有 virtual DOM,但是又不想直接暴露 DOM 进行操作,因此就像 ElementRef 一样,它对将整个 视图(view) 进行了一个抽象,创建了 ViewContainerRef 以方便管理与 directive 绑定的,整个 DOM 的 view/template

setter

set 是一个 JavaScript 的语法糖,这也是 ES6 后出现的语法,与 Angular 无关。

这里 Angular 动态的将其 setter 和 @Input 进行绑定,并且提供一个更加直观且简洁明了的方式对当前与 @Input 绑定的值进行变化管理。如果不使用 setter 的话,也可以在对应的 ngOnChangesngOnInit 中监听值的变化,并且进行对应的操作。

整个 setter 中做的操作就分为两步:

  1. isLoading = true

    这个情况下需要渲染一个 loading spinner——这在构造函数中就已经创建好了,并且使用 renderer 去进行渲染

    当前的 nativeElement 指向的是 <div *appLoading="isLoading"></div> 这个具体的元素

    因此这里的操作就是在 nativeElement 的父元素下,新增一个 loading spinner

  2. isLoading = false

    这个情况下 loading spinner 被移除,原本的 template view 被渲染

💡:至于选用 renderer 就是因为在 attribute directive 部分已经讲过了,而且实现起来比较方便。我找了一下不用 renderer 渲染的方式,需要用到 ComponentFactoryResolver,这个暂时就还没学上,等之后学上了再说

structural directive 幕后

如果看了官方文档就会知道,<div *ngIf="hero" class="name">{{hero.name}}</div> 这样的语法是一个缩写,本质上它的实现方法如下:

<ng-template [ngIf]="hero"><div class="name">{{hero.name}}</div>
</ng-template>

随后就像上面写过的 structural directive 的实现一样,ngIf 通过属性绑定被 @Input 监听到,structural component 再根据业务逻辑进行制定渲染。

ng-template 本身也是一个 view 的 placeholder,它是不会被渲染的

下面是官方文档关于 ngFor 的实现:

<div*ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"[class.odd]="odd"
>({{i}}) {{hero.name}}
</div><ng-templatengForlet-hero[ngForOf]="heroes"let-i="index"let-odd="odd"[ngForTrackBy]="trackById"
><div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>

也就是鉴于这样的转化,所以 structural directive 的语法一定是 *[directive_name]="[variable]" 这样的实现

第一个 Angular 项目——实现下拉框

项目里面没导入对应的 js 文件,所以现在 bootstrap 的 dropdown 是没办法被触发的。这里就是用 directive 去解决这个问题

新建 directive

❯ ng generate directive directives/dropdown --skip-tests
CREATE src/app/directives/dropdown.directive.ts (145 bytes)
UPDATE src/app/app.module.ts (1200 bytes)

实现 dropdown directive

这里需要了解一下 bootstrap 的 dropdown 是怎么被展开的——实际上是通过 open 这样一个 class 去实现的。因此,当 class 为 btn-group open 时,下拉框时展开的,当 class 为 btn-group 时,下拉框时关闭的。

所以这里需要实现一个 attribute directive,去管理 class 即可

最初的实现方法为:

export class DropdownDirective {constructor(private el: ElementRef, private renderer: Renderer2) {}@HostListener('click')onClick() {if (this.el.nativeElement.classList.contains('open')) {this.renderer.removeClass(this.el.nativeElement, 'open');} else {this.renderer.addClass(this.el.nativeElement, 'open');}}
}

不过一个简化的方法是使用 @HostBinding 去进行操作:

export class DropdownDirective {@HostBinding('class.open') isOpen = false;@HostListener('click')onClick() {this.isOpen = !this.isOpen;}
}

二者实现的效果是一样的:

在这里插入图片描述


补充一下点击 HTML 任何地方关闭 dropdown 的实现:

export class DropdownDirective {@HostBinding('class.open') isOpen = false;@HostListener('document:click', ['$event'])onClick(evemt: MouseEvent) {this.isOpen = this.el.nativeElement.contains(evemt.target)? !this.isOpen: false;}constructor(private el: ElementRef) {}
}

在这里插入图片描述

reference

没有特殊标注的都是来自官方文档的内容

  • Structural directives
  • TemplateRef
  • ViewContainerRef

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/699472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

排序和查找算法

一、排序算法 1.快速排序 不稳定&#xff0c;时间复杂度最理想 O(nlogn) 最差时间O(n^2) package com.test;public class fasf{/*** 快速排序* param args*/public static void main(String[]args){//不用设置大小int [] num{3,6,5,4,7,2,9};fasf fnew fasf();f.quicksort(n…

ZS Associates致盛咨询是什么公司?排名怎么样?

随着商业化时代的加速演进&#xff0c;咨询公司在企业发展中的“智囊团”角色愈发突显。对于医药企业来说&#xff0c;一个优秀的咨询团队不仅可以帮助推动整体战略转型及内部改革&#xff0c;还对药品研发、营销起到优化促进作用。 那什么样的咨询企业可称之为优秀的咨询企业…

6.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-通过逆向分析确定游戏明文发送数据过程

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;测试需求与需求拆解 在开始之前要了解一个小知识&#xff0c;在逆向开始之前要很清楚知道要找的东西是什么&#xff0c;大概长什么样子&#xff0c;只有这样才能看到它第一眼发现它&#xff0c;现在我…

129 Linux 系统编程7 ,make 的编写和解析

前文中&#xff0c;我们有多少个.c文件&#xff0c;就需要build 出来多少个.o文件 假设我们的项目很大&#xff0c;怎么管理这些 .c文件呢&#xff1f; 这里就要学习一个make文件的编写了。 makefile 本质上是一个脚本语言 脚本语言实际上就是将一系列命令放在一起执行 mak…

Jetson Xavier NX 与笔记本网线连接 ,网络共享,ssh连接到vscode

Jetson Xavier NX 与笔记本网线连接 &#xff0c;网络共享&#xff0c;ssh连接到vscode Jetson Xavier NX桌面版需要连接显示屏、鼠标和键盘&#xff0c;操作起来并不方便&#xff0c;因此常常需要ssh远程连接到本地笔记本电脑&#xff0c;这里介绍一种连接方式&#xff0c;通过…

如何不患心肌梗塞

目录 一&#xff0c;个人面板 二&#xff0c;公共版图 三&#xff0c;卡牌 1&#xff0c;食物牌 2&#xff0c;药物牌 3&#xff0c;事件牌 四&#xff0c;回合操作 1&#xff0c;起始玩家 2&#xff0c;一轮操作 3&#xff0c;个人回合 4&#xff0c;轮末结算 5&a…

Vision Transfomer系列第二节---Tricks测试

目录 学习式和固定式位置编码测试dropout的作用测试block深度的作用测试embeding维度大小的作用测试多头的作用测试Overlap Patch的作用 学习式和固定式位置编码测试 主要测试无位置编码\可学习位置编码和固定式位置编码的训练效果: 其中固定式位置编码采用之前博客的正余弦位…

第十一天-Excel的操作

目录 1.xlrd-Excel的读模块 安装 使用 获取工作簿 读取工作簿的内容 xlsxwriter-Excel的写模块 安装 使用 生成图表 add_series参数 图表的样式 demo&#xff1a;生成图表 Excel的操作在python中有多个模块&#xff0c;为了能够快速使用&#xff0c;选择了相对简单…

【Docker】初学者 Docker 基础操作指南:从拉取镜像到运行、停止、删除容器

在现代软件开发和部署中&#xff0c;容器化技术已经成为一种常见的方式&#xff0c;它能够提供一种轻量级、可移植和可扩展的应用程序打包和部署解决方案。Docker 是目前最流行的容器化平台之一&#xff0c;它提供了一整套工具和技术&#xff0c;使得容器的创建、运行和管理变得…

想设计智能手环,我需要设计哪种电路?

随着电子技术的高速发展&#xff0c;可穿戴设备逐渐火爆&#xff0c;其中之一是智能手环&#xff0c;作为现代可穿戴技术的热门产品之一&#xff0c;它集成了多种功能&#xff0c;如健康检测、运动跟踪、通知提醒等&#xff0c;为了实现这些功能&#xff0c;需要用上哪些电路模…

模板注入 [WesternCTF2018]shrine1

打开题目 直接查看源代码 发现注册了一个名为FLAG的config&#xff0c;这里可能有flag&#xff0c; 存在flask-jinja2模板注入&#xff0c; 并且存在黑名单过滤 输入shrine/{{7*7}}验证成功 通过url_for()与globals()函数&#xff0c;绕过黑名单 /shrine/{{url_for.__globa…

Android 输入法框架简介

每种平台都有自己的输入法框架. GNU/Linux 桌面环境有多种输入法框架, 比如 ibus, fcitx 等. 但是 Android 操作系统只有一种, 是统一提供的输入法框架. 相关链接: 《ibus 源代码阅读 (1)》 https://blog.csdn.net/secext2022/article/details/136099328https://developer.and…

2024年 最新python调用ChatGPT实战教程

2024年 最新python调用ChatGPT实战教程 文章目录 2024年 最新python调用ChatGPT实战教程一、前言二、具体分析1、简版程序2、多轮对话3、流式输出4、返回消耗的token 一、前言 这个之前经常用到&#xff0c;简单记录一下,注意目前chatgpt 更新了&#xff0c;这个是最新版的&am…

MIT-BEVFusion系列九--CUDA-BEVFusion部署4 c++解析pytorch导出的tensor数据

目录 创建流打印 engine 信息打印结果内部流程 启动计时功能加载变换矩阵并更新数据&#xff08;重要&#xff09;内部实现 该系列文章与qwe一同创作&#xff0c;喜欢的话不妨点个赞。 在create_core方法结束后&#xff0c;我们的视角回到了main.cpp中。继续来看接下来的流程。…

挑战杯 基于大数据的时间序列股价预测分析与可视化 - lstm

文章目录 1 前言2 时间序列的由来2.1 四种模型的名称&#xff1a; 3 数据预览4 理论公式4.1 协方差4.2 相关系数4.3 scikit-learn计算相关性 5 金融数据的时序分析5.1 数据概况5.2 序列变化情况计算 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &…

【操作系统】14.I/O设备怎么分配和回收?

5.2 I/O设备怎么分配和回收&#xff1f; 5.2.1 I/O核心子系统 I/O调度 设备保护 假脱机技术&#xff08;SPOOLing技术&#xff09; ​ 输入井和输出井 ​ 输入进程和输出进程 ​ 输入缓冲区和输出缓冲区 设备分配与回收 ​ 设备分配应考虑的因素 ​ 静态分配与动态分配 ​ 设备…

Runaway Queries 管理:提升 TiDB 稳定性的智能引擎

在数字化系统扮演重要角色的今天&#xff0c;数据库稳定性成为企业关注的核心问题。对于重要计算机系统而言&#xff0c;突发的性能下降可能对业务造成不可估量的损失。为了稳定数据库性能&#xff0c;用户可以从管理流程入手规范变更的测试&#xff0c;或者利用产品手段减少预…

3DIoUMatch: Leveraging IoU Prediction for Semi-Supervised 3D Object Detection

3DIoUMatch: Leveraging IoU Prediction for Semi-Supervised 3D Object Detection 论文链接&#xff1a;https://arxiv.org/pdf/2012.04355.pdf 代码链接&#xff1a;https://github.com/yezhen17/3DIoUMatch 作者单位&#xff1a;Stanford University等 发表平台&#xff1a;…

【JavaEE】_Servlet程序的编写方法

目录 1. 创建项目 2. 引入依赖 3. 创建目录结构 3.1 在main目录下创建一个webapp目录 3.2 在webapp目录下创建一个WEB-INF目录 3.3 在WEB-INF目录下创建一个web.xml文件 3.4 在web.xml中进行代码编写 4. 编写代码 4.1 在java目录下创建类 4.2 打印"hello world&…

无人机精准定位技术,GPS差分技术基础,RTK原理技术详解

差分GPS的基本原理 差分GPS&#xff08;Differential GPS&#xff0c;简称DGPS&#xff09;的基本原理是利用一个或多个已知精确坐标的基准站&#xff0c;与用户&#xff08;移动站&#xff09;同时接收相同的GPS卫星信号。由于GPS定位时会受到诸如卫星星历误差、卫星钟差、大…