ng15 指令组合 API(directive composition API)

指令组合 API(directive composition API) 将代码复用提升到另一个高度!该功能的灵感来自 GitHub 上最受欢迎的 Issue feature request,该 Issue 要求提供向 Host Element 添加指令的功能。指令组合 API 使开发人员能够使用指令增强 Host 元素能力,并为 Angular 提供了强大的代码复用策略,这只有通过我们的编译器才能实现,指令组合 API 仅适用于独立指令。让我们看一个简单的示例

@Component({
    
    
  selector: 'mat-menu',
  hostDirectives: [HasColor, {
    
    
    directive: CdkMenu,
    inputs: ['cdkMenuDisabled: disabled'],
    outputs: ['cdkMenuClosed: closed']
  }]
})
class MatMenu {
    
    }

在上面的代码片段中,我们用两个指令HasColor和CdkMenu增强了 MatMenu。MatMenu使用HasColors指令的所有输入、输出和相关逻辑,同时通过 inputs 和 outputs 参数控制仅使用CdkMenu的 cdkMenuDisabled 和 cdkMenuClosed 部分逻辑。这种技术可能会让你想起某些编程语言中的多重继承特性,区别在于我们有一种解决名称冲突的机制,它适用于用户界面。

首先介绍一下什么是指令组合 API,点击 Directive composition API 查看官方文档,如果已经很熟悉了可以忽略这个章节。Angular 框架提供了一种其他框架基本没有或者只是阉割版的特性叫『指令』,那么指令提供了一种封装可复用逻辑的行为(可以将属性、CSS 类和事件侦听器应用于元素)。
比如们要开发一个简单的 appColor 指令给任意元素设置 color 颜色的行为,代码实现如下:

@Directive({
    
    
  selector: '[appColor]',
  standalone: true,
})
export class AppColor implements OnInit {
    
    
  @Input() color!: string;private elementRef = inject(ElementRef);ngOnInit() {
    
    
    this.elementRef.nativeElement.style.color = this.color;
  }
}

这样我只要在任意元素上绑定 appColor 指令并传入 color=“颜色值” 就可以设置字体颜色:

@Component({
    
    
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, AppColor],
  template: `
    <span appColor color="red">Hello from Angular!</span>
  `,
})
export class App {
    
    }

展示效果如下:
在这里插入图片描述
那么这个 appColor 指令就是给绑定的 span 元素上设置 style.color Attribute,除此之外还可以设置其他的 Attribute、CSS Class,监听 Dom 事件等操作,对于 appColor 指令来说,绑定的 span 元素就是 Host 宿主元素。
接下来再次创建一个新的指令 appBgColor 给宿主元素添加背景色,代码实现如下:

@Directive({
    
    
  selector: '[appBgColor]',
  standalone: true,
})
export class AppBgColor implements OnInit {
    
    
  @Input() bgColor!: string;private elementRef = inject(ElementRef);ngOnInit() {
    
    
    this.elementRef.nativeElement.style.backgroundColor = this.bgColor;
  }
}

那么此时想要同时给元素上同时使用 appColor 和 appBgColor 指令,就需要使用者绑定两个指令,且传入每个指令的参数:

@Component({
    
    
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, AppColor, AppBgColor],
  template: `
     <span appColor color="#fff" appBgColor bgColor="#6698ff">
       Hello from Angular!
     </span>
  `,
})
export class App {
    
    }

这里的两个指令都只有一个输入参数,为了示例更有说服力,我特意把参数的命名和指令的 selector 区分开,其实我们最终实现的时候是可以把指令的 selector 和 input 都定义成一样的命名,这样使用的时候只需要传递一个参数即可,比如:appColor=“#fff” appBgColor=“#6698ff”

现在我们想要新曾一个 appText 指令同时支持设置字体颜色和背景色,那么在没有指令组合 API 之前我们可以通过如下几种方案:
复制重复代码,对于很多代码的指令来说,重复是一个万恶之源
通过继承实现,但是继承只能继承单个类,这让组合多个类被限制了
通过 Mixins 模式实现,Angular material 组件库中大量使用了此特性,参考 tab 的 Mixin 代码

那么上述三种方案都不完美,现在可以通过 hostDirectives 组合之前的 appColor 和 appBgColor 指令,达到逻辑的复用,代码如下:

@Directive({
    
    
  selector: '[appText]',
  hostDirectives: [
    {
    
    
      directive: AppColor,
      inputs: ['color: textColor'],
    },
    {
    
    
      directive: AppBgColor,
      inputs: ['bgColor'],
    },
  ],
  standalone: true,
})
export class AppText {
    
     }

这样我们就可以通过如下的方式使用 appText 指令:

@Component({
    
    
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, AppColor, AppBgColor, AppText],
  template: `
    <div appText textColor="#fff" bgColor="#c9584e">Hello Directive composition API </div>
  `,
})
export class App {
    
    }

以上 hostDirectives 就是指令组合 API Directive composition API,目前我们只是在指令(appText)上组合指令,那么在平时的开发中我们经常会遇到要给组件组合多个指令,比如我们要写一个 app-title 的组件,这个组件会展示标题,但是使用的时候需要给组件设置字体颜色和背景色的功能,第一种做法就是让使用者自行组合 app-title 组件 + appColor 和 appBgColor 指令使用:

<app-title appColor color="#fff" appBgColor bgColor="#6698ff"></div>

但是这样太繁琐了,我们希望 app-title 组件直接传入颜色和背景色,甚至还有默认颜色和背景色的功能,希望直接这样使用:

<app-title />
等同于
<app-title color="#fff" bgColor="#6698ff" />

color 不传默认是 #fff,背景色默认是 #6698ff。
那么通过 hostDirectives 实现 app-title 组件的代码如下:

@Component({
    
    
  selector: 'app-title',
  template: 'PingCode',
  hostDirectives: [
    {
    
    
      directive: AppColor,
      inputs: ['color'],
    },
    {
    
    
      directive: AppBgColor,
      inputs: ['bgColor'],
    },
  ],
  standalone: true,
})
export class AppTitle {
    
    
  appColor = inject(AppColor);
​
  appBgColor = inject(AppBgColor);constructor() {
    
    
    this.appColor.color = this.appColor.color || '#fff';
    this.appBgColor.bgColor = this.appBgColor.bgColor || '#6698ff';
  }
}

模板使用如下:

import 'zone.js/dist/zone';
import {
    
     Component } from '@angular/core';
import {
    
     CommonModule } from '@angular/common';
@Component({
    
    
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, AppColor, AppBgColor, AppText, AppTitle],
  template: `
    <app-title />
    <br /> 
    <br />
    <app-title textColor="#fff" bgColor="#c9584e" />
  `,
})
export class App {
    
    }

展示效果如下:

在这里插入图片描述

这里有几个知识点需要注意:
hostDirectives 可以直接通过 [AppColor, AppBgColor] 数组在 app-title 组件或者 appText 指令中组合AppColor 和 AppBgColor 指令,这样默认所有的输入参数(Input)和输出参数(Output)都无效,必须要显示通过 { directive: AppColor, inputs: [‘color’], outputs: [‘colorChange’] } 指定输入和输出参数
通过 inputs 和 outputs 指定参数的时候还可以起别名,比如: { inputs: [‘color: textColor’] },这样在 app-title 组件或者 appText 指令中传递的 Input 参数是 textColor ,而非 color
组合指令只能在编译阶段处理,无法在运行时给某个组件或者指令组合其他的指令
hostDirectives 被设置指令必须是独立指令,也就是 standalone 必须设置为 true,假如给 app-title 上绑定 hostDirectives: [AppColor],这里需要保证 AppColor 指令是 standalone true 的,app-title 不需要是独立组件
hostDirectives 会组合指令的行为,hostDirectives 中声明的指令 selector 会失效,以当前组件/指令的 selector 为主,这个很好理解,因为我们只是要组合指令的复用逻辑,selector 肯定不需要组合

最后总结
Directive composition API 是一个锦上添花的功能,没有这个新特性我们也可以通过继承和 Mixin 解决过去遇到的问题,可能方案会繁琐一点,样板代码多一点,但是有了这个组合指令后,让指令可以自由组合,这对于业务开发来说帮助是极大的,同时可能对于大家做基础组件要有一定的要求,不仅仅要考虑模板中使用,还要考虑是否可以被其他组件组合使用,比如我们过去写的布局组件 thy-layout 可能需要新增一个 thyLayout 指令,然后 thy-layout 组件通过 hostDirectives: [thyLayout] 复用 thyLayout 指令的行为。
当然这里也不是说组合指令 API 就没有缺点,我个人认为有以下几点问题:
指令组合 API 必须依赖于 Angular 的编译器,给组件组合了多个指令后,虽然组件可以传入指令的参数,但是组件 Class 类上并没有 Input 属性,只能通过 inject(指令1) 注入到组件中方可使用
无法运行时动态组合,灵活性不是很强

猜你喜欢

转载自blog.csdn.net/m0_54944506/article/details/139634089