Angular 18+ 高级教程 – Component 组件 の Attribute Directives 属性型指令
介绍
指令就是没有模板的组件。除了模板其它的都有,比如 selector、inject、@Input、lifecycle 等等。
那既然都有完整的组件了,为什么还搞一个少掉模板的指令呢?
很简单啊,因为就是不需要模板丫。没道理不需要还硬硬要放吧。
组件是 JS、CSS、HTML 的封装,但很多时候,我们可能只想要封装 JS 和 CSS。
这个时候就可以使用指令啦。
Attribute Directives & Structural Directives
指令有两大类, 一个叫 Attribute Directives,另一个叫 Structural Directives。
Structural Directives 属于动态 Element 的范畴,比较复杂,而且需要一些 ng-template,ng-container 的预备知识。所以这篇不会讲解。我们留给下一篇。
这篇我们主要讲解 Attribute Directives 就好了。
Attribute Directives
我们直接从例子中学习呗。
我想封装一个 JS + CSS 的功能,效果是当某个 element 被 hover 时,它的字会变成红色。
这个 element 可以是 h1-h6、p、li、a 等等。如果使用组件来实现,那感觉就很怪,你都不知道模板要写啥。
来看看指令呗
ng g d highlight-on-hover
用 CLI 创建一个指令,d 是 directive 的缩写。
@Directive({
selector: '[appHighlightOnHover]',
standalone: true,
})
export class HighlightOnHoverDirective {}
它和组件一样,都是一个 class,selector 通常是 attribute (组件则通常是 tag。 对,只是通常而已,其实指令也可以是 tag,组件也可以是 attribute)。
另外提一个冷知识,attribute 'appHighlightOnHover' 最后在 element 上会变成 lowercase <div apphighlightonhover>。
通过 inject 获取到当前的 element。
private element: HTMLElement = inject(ElementRef).nativeElement;
这样我们就可以对 element 做操作了。
加上两个核心方法, setColor 和 clearColor。
setColor() {
this.element.style.color = 'red';
} clearColor() {
this.element.style.removeProperty('color');
}
指令没有模板,也就不存在 MVVM 的概念。在这里直接操作 DOM 是正确的。
补上一个 transition 过度 (让体验好一下)
constructor() {
this.element.style.transition = 'color 0.4s';
}
注意:如何我们项目需要做服务端渲染 (Server-side rendering),那就不可以在 constructor 阶段直接操作 DOM。
必须使用 Renderer 做渲染。
constructor() {
const renderer = inject(Renderer2);
// constructor 阶段需要使用 Renderer2
renderer.setStyle(this.element, 'transition', 'color 0.4s');
}
完整的 class
export class HighlightOnHoverDirective {
private element: HTMLElement = inject(ElementRef).nativeElement; constructor() {
const renderer = inject(Renderer2);
// constructor 阶段需要使用 Renderer2
renderer.setStyle(this.element, 'transition', 'color 0.4s');
} setColor(): void {
// 这里不需要使用 Renderer2,可以直接操作 DOM
this.element.style.color = 'red';
} clearColor(): void {
// 这里不需要使用 Renderer2,可以直接操作 DOM
this.element.style.removeProperty('color');
}
}
最后通过 metadata 加上 host listening
@Directive({
selector: '[appHighlightOnHover]',
standalone: true,
host: {
'(mouseenter)': 'setColor()',
'(mouseleave)': 'clearColor()',
},
})
使用它
<div class="card">
<h1 appHighlightOnHover>Hello World</h1>
<p appHighlightOnHover>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus, minima.</p>
</div>
在任何 element 添加上 appHighlightOnHover 就可以了。记得要添加 @Component.imports
效果
Host binding and with class / style binding
上面例子中,指令使用到了 host listening 技术。
若我们想使用 host binding 自然也没问题,下面给一个 class binding 和 style binding 的例子
host: {
// 这是 host binding
style: 'color: red; z-index: 1', // 注:z-index 是 kebab-case // 这是 host binding + style binding
'[style.backgroundColor]': `'blue'`, // 注:backgroundColor 是 lowerCase, 'blue' 有 quote // 这是 host binding
class: 'my-class, my-class2', // 这是 host binding + class binding
'[class.my-class-3]': 'true',
},
效果
指令 @Input
指令和组件一样,可以有 @Input @Output,这里给一个 @Input 的例子。
export class HighlightOnHoverDirective {
private element: HTMLElement = inject(ElementRef).nativeElement; @Input({ alias: 'appHighlightOnHover', required: true })
color!: string; setColor() {
this.element.style.color = this.color;
} clearColor() {
this.element.style.removeProperty('color');
} constructor() {
const renderer = inject(Renderer2);
renderer.setStyle(this.element, 'transition', 'color 0.4s');
}
}
调用
<div class="card">
<h1 appHighlightOnHover="red">Hello World</h1>
<p [appHighlightOnHover]="'blue'">Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus, minima.</p>
</div>
效果
冷知识 – @Input 会导致 attribute 消失
两个知识点:
lowercase
HighlightOnHover 指令的 selector 是 '[appHighlightOnHover]'
我们在 App Template 写的是 camelCase
<h1 appHighlightOnHover="red">
最终的 DOM attribute 变成了 apphighlightonhover="red" lowercase。
attribute 消失了
上图的 <p> 完全没有 apphighlightonhover="blue",因为在 App Template 写的是 binding syntax
p [appHighlightOnHover]="'blue'">
这会导致最终的 DOM 连 attribute 都没有。(when using binding syntax + @Input alias same name as selector 就会出现这种现象)
如果我们希望它一定有 attrubute 的话,可以透过 host binding attribute 补上去
效果
综上两点,如果想用指令 attribute 作为 CSS selector 要特别留意哦。
Angular Build-in Attribute Directives (ngClass, ngStyle)
我们之前学过 class bindding 和 style binding,当时有说过,它们不适合处理 multiple 的场景。multiple 场景比较适合用 ngClass 和 ngStyle 属性型指令。
ngClass
<h1 [ngClass]="'abc efg'">Hello World</h1>
<h1 [ngClass]="['abc', 'efg', '']">Hello World</h1> <!--null, undefined is not allowed-->
<h1 [ngClass]="{ abc: true, efg: false, xyz: 1 }">Hello World</h1> <!--null, undefined is not allowed-->
ngClass 和 class binding 几乎是一样的,几个点注意一下:
支持输入 string, array, object
array 内不能有 null 和 undefined, empty string 则可以(会被 ignore)
object 的属性值会被强转成 boolean,所以你要用 number 0 | 1 来表示 true false 也是可以的。
object 不需要 immutable(class binding 则需要)
ngStyle
<h1 [ngStyle]="{ 'width.px' : 100, height: '100px', color: null }">Hello World</h1>
ngStyle 和 style binding 几乎是一样的, 几个点要注意一下
支持输入 object 而已
object 不需要 immutable(style binding 则需要)
object 属性 支持 suffix unit(style binding 传入 object 是不支持的)
属性型指令 host binding with ngClass / ngStyle?
host: {
class: 'a, b, c', // this work
'[ngClass]': '{ x: true, y: false, z: 1 }', // this not work
},
指令想 host binding 其它指令需要使用 Directive composition API (下一 part 会教),然而它也有一些 limitation,像 ngClass / ngStyle 是无法被 host binding 的。
Directive composition API
使用指令的方法是把指令写到 element 上。那如果我有一个组件,我能把指令写到 host 上吗?
我们之前学过 Host Binding and Listening,想当然的会认为肯定没有问题呀。
但 Angular 其实一直到 v15.0 才实现了这个功能。
<app-my-h1 appHoverColor></app-my-h1>
我们希望把 appHoverColor 封装到 app-my-h1 里。
@Component({
selector: 'app-my-h1',
standalone: true,
imports: [CommonModule, HoverColorDirective],
templateUrl: './my-h1.component.html',
styleUrls: ['./my-h1.component.scss'],
hostDirectives: [
{
directive: HoverColorDirective,
},
],
})
export class MyH1Component {}
通过组件的 metadata hostDirectives 属性做配置。
注:它不是用 HostBinding 哦。HostBinding 只能用于原生 HTML 属性,不可以用于 @Input 也不可以用于指令。
提醒1:hostDirectives 一定要是 Standalone Component 哦。
提醒2:hostDirectives 输出的指令是不带 selector 的,比如说 HoverColorDirective 的 selector 是 [appHorverColor],MyH1 组件用 hostDirectives 声明了 HoverColorDirective,最终 <app-my-h1> 并不会出现 appHorverColor attribute。
指令 @Input
如果指令需要 input 那就有点麻烦了。
export class HighlightOnHoverDirective {
@Input({ required: true })
color!: string;
}
指令需要一个 color input。
首先,Angular 目前只提供了一条路来输入 input。
我们需要在 hostDirectives re-expose 这个 input。
hostDirectives: [
{
directive: HighlightOnHoverDirective,
inputs: ['color'], // re-expose input
// inputs: ['color: my-color'], 通过分号还可以换一个属性名
},
],
最后在使用组件时传入
<app-my-h1 color="red"></app-my-h1>
re-expose Input 的问题
显然强制 re-expose 是不逻辑的,难道我们不可以在组件内封装指令的 input 逻辑吗?
目前,Angular 没有给出其它路,虽然我们确实有方法可以做到这一点。
export class MyH1Component {
constructor() {
inject(HighlightOnHoverDirective).color = 'red'; // 如果是 input Signal 会更麻烦一点
const highlightOnHoverDirective= inject(HighlightOnHoverDirective);
const colorInputSignalNode = highlightOnHoverDirective.color[SIGNAL];
colorInputSignalNode.applyValueToInputSignal(colorInputSignalNode, 'red');
}
}
像上面这样就可以在不 re-expose input 的情况下,set value to input 了。
但...通常 Angular 没有给出一条路,是因为他们没有想到...或者认为没有那么重要。所以很容易出 bug。
比如...指令的 input 如果设置了 required,那会报错。参考: Issue: required inputs shouldn't always have to be exposed by composed directive。
另外,上面这种方式严格的讲是叫 "给组件实例属性赋值",而不是 "给组件 set @Input value",这两者区别还是挺大的,比如 @Input 的 transform 会失效,不会触发 ngOnChanges 等等问题。
总之,近期内 Angular Team 视乎没有意愿去改善这个问题
为此我还特意搜了一下 Angular Material 源码。结果发现里头完全没有使用 hostDirectives 功能...难怪他们没有意愿改进...
目录
上一篇 Angular 18+ 高级教程 – Component 组件 の Template Binding Syntax
下一篇 Angular 18+ 高级教程 – Component 组件 の Pipe 管道
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular 18+ 高级教程 – Component 组件 の Attribute Directives 属性型指令的更多相关文章
- angular2系列教程(四)Attribute directives
今天我们要讲的是ng2的Attribute directives.顾名思义,就是操作dom属性的指令.这算是指令的第二课了,因为上节课的components实质也是指令. 例子
- vue 基础-->进阶 教程(3):组件嵌套、组件之间的通信、路由机制
前面的nodejs教程并没有停止更新,因为node项目需要用vue来实现界面部分,所以先插入一个vue教程,以免不会的同学不能很好的完成项目. 本教程,将从零开始,教给大家vue的基础.高级操作.组件 ...
- Angular CLI 使用教程指南参考
Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...
- 一篇文章看懂angularjs component组件
壹 ❀ 引 我在 angularjs 一篇文章看懂自定义指令directive 一文中详细介绍了directive基本用法与完整属性介绍.directive是个很神奇的存在,你可以不设置templa ...
- 分享25个新鲜出炉的 Photoshop 高级教程
网络上众多优秀的 Photoshop 实例教程是提高 Photoshop 技能的最佳学习途径.今天,我向大家分享25个新鲜出炉的 Photoshop 高级教程,提高你的设计技巧,制作时尚的图片效果.这 ...
- Salesforce Lightning开发学习(二)Component组件开发实践
lightning的组件区分标准组件.自定义组件和AppExchange组件.标准组件由SF提供,自定义组件由developer自行开发,AppExchange组件由合作伙伴建立.下面我们写一个简单的 ...
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- angular里使用vue/vue组件怎么在angular里用
欢迎加入前端交流群交流知识&&获取视频资料:749539640 如何在angularjs(1)中使用vue参考: https://medium.com/@graphicbeacon/h ...
- angularjs中directive指令与component组件有什么区别?
壹 ❀ 引 我在前面花了两篇博客分别系统化介绍了angularjs中的directive指令与component组件,当然directive也能实现组件这点毋庸置疑.在了解完两者后,即便我们知道co ...
- Pandas之:Pandas高级教程以铁达尼号真实数据为例
Pandas之:Pandas高级教程以铁达尼号真实数据为例 目录 简介 读写文件 DF的选择 选择列数据 选择行数据 同时选择行和列 使用plots作图 使用现有的列创建新的列 进行统计 DF重组 简 ...
随机推荐
- 【译】You probably should stop using a custom TaskScheduler
来自Sergey Tepliakov的 https://sergeyteplyakov.github.io/Blog/csharp/2024/06/14/Custom_Task_Scheduler.h ...
- 数学工具 | 如何将图片公式快速输入到Word中?
背景: 在日常科研.学习与工作中,我们可能需要使用到某些书籍.期刊或者规范上的公式,但是如果自己纯手打则会相当麻烦(数学系LaTeX高手请忽略),因此如果有工具能够解决这个问题,那真的是解决了一大痛点 ...
- tp 模型hasOne、hasMany、belongsTo详解
首先,这3个的大致中文意思:hasOne:有一个,加上主谓语应该是 ,A 有一个 BhasMany:有很多,A 有很多 BbelongsTo:属于, A 属于 B这里我们准备3张表来理解他们的关系:u ...
- Centos7 安装 rabbitmq-server-3.7.7 图文教程
下载 rabbitmq-server wget https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.7/rabbitmq-server-3. ...
- SSH指定用户登录与限制
环境准备 :::info 实验目标:ServerA通过用户ServerB(已发送密钥和指定端口) ::: 主机 IP 身份 ServerA 192.168.10.201 SSH客户端 ServerB ...
- VUE小知识~作用域插槽
作用域插槽可以为我们向组件内插入特定的标签,方便修改维护. 组件内需要使用 <slot></slot>进行插槽站位. 组件标签内需要使用<template > &l ...
- 关于Script的猜想和代码设计
由于现在接触的是蓝图,而之前接触的脚本,这两者有些不一样. 对脚本的设计如果是代码的解析的话, 对蓝图的设计则需要提供一些底层的API. 变量分为: 基础类型 ,复合类型 ,容器类型 NewGlob ...
- 【AppStore】IOS应用上架Appstore的一些小坑
前言 上一篇文章写到如何上架IOS应用到Appstore,其中漏掉了些许期间遇到的小坑,现在补上 审核不通过原因 5.1.1 Guideline 5.1.1 - Legal - Privacy - D ...
- Linux系统下查找安装包所在目录
Linux系统下查找安装包所在目录 想知道Linux系统下安装了哪些软件包,以及软件包安装在哪个目录下,可以用以下命令 1. which which命令查找出相关命令是否已经在搜索路径中,例子如下:$ ...
- Mysql将查询出的数值转换为中文显示case..when..then
我们经常需要在数据库导出文件,可是导出某些字段时不是中文含义其它同事分不清.可以通过case..when..then根据一一对应的关系将值转成中文,再进行导出方便大家查阅. 1.正常sql未处理之前查 ...