当前位置: 首页 > news >正文

Angular由一个bug说起之十九:Angular 实现可拓展 Dropdown 组件

请添加图片描述

目录:

  1. 项目中的 Dropdown
  2. 简要分析需求
  3. 设计方案
  4. Demo 展示
  5. 验证

项目中的 Dropdown
我公司的项目中实现过不止一个 Dropdown 组件,但是普遍存在一些难以解决的痛点。又或者在实现的时候能满足需求,但是一旦功能修改了就很难拓展。这也是写这篇文章的原因,主要是想找出一个比较好的解决方案。
以下我列举一些项目中典型的问题以及它们出现的原因:

  1. 多份 options 备份
  2. 多组件复合
  3. 复杂的组件嵌套

多份 options 备份
这个问题最先出现的原因是我们引入了检索功能。被检索的 options 是不完整的列表,当我们需要从列表中筛选出被选中的选项来改变它们的状态时需要一个完整的 options。所以此时就多了一个 filteredOptions。
而后随着功能越来越复杂,这类的 options 备份也越来越多,甚至曾经一度达到了 4-5 个。

多组件复合
一开始我们只需要实现一个单选下拉框和一个复选下拉框。而后功能增加例如: lazyload options,配合其它功能的Switch开关,Search 输入框 等。
这就造成了组件的布局问题,以及各个功能之间的通信复杂度也上升了。慢慢的 Dropdown 的体量变的越来越大,它的维护难度也直线上升。

复杂的组件嵌套
我们一开始实现的单选和多选 dropdown 还是有很多可以复用的组件和样式的。后期二者的区别越来越大,共用的组件慢慢变的不再共用。所以项目中出现了很多的 if else 来区分这两个组件的应用范围。
同时这两个组件当时是嵌套在同一个组件中,因为它们有类似的数据流,可以共享通信过程。但是这也为后期的功能拓展埋下了隐患。


综上所述,正因为存在这样的问题所以我们需要一个更好的 dropdown。为下次项目重构提前做出准备。

简要分析需求
Dropdown 组件可涵盖的业务非常广泛,要具有 和业务解耦,扩展性强,好维护 等特性。我们列出不同视角下,组件的特性和规律,方便我们制定方案。

组件有什么?
以 Angular component 为例,一个组件至少需要以下 4 点:

  1. input
  2. output
  3. UI (html, css)
  4. functions (event, method …)

这 4 点可以归纳为:

  1. input AND output --> Model
  2. UI --> View
  3. functions --> Controller

如何解耦
由此可见,实现一个扩展性强的组件我们需要做的是:协调好 mvc 的逻辑,明确 mvc 的代码应该放在哪些文件里,用什么形式实例化组件。
那么,为了实现解耦,为了能以 API 热插拔的形式实现组件的拓展,就有一个核心的要求:Dropdown 中的每一个小功能要以一个 独立的,完整的 包含全套 mvc 的组件形式存在。

用代码表示类似于:

<!-- Dropdown UI -->
<div class="dropdown"><SearchInput></SearchInput><DropdownMenu></DropdownMenu><DropdownBtns></DropdownBtns>
</div>

只有功能完整且独立才能不受到其它功能的影响,这是目前我能想到的唯一的解耦方式。

设计方案
上一步已经分析出了实现组件要遵循的核心原则,方案设计要敲定实现的方式。
我实现了两个方案,第一个方案在实现后有缺陷所以废弃了,最后采纳了第二个方案。

第一次方案的实现
实现方式要考虑到以下几个问题:

  1. 组件布局
  2. 各个功能之间的交互
  3. 热插拔怎么实现

有两个方案能满足上述要求:

  1. 使用
  2. 动态创建组件

它们各自的优缺点:

  1. ng-content
    ● 优点:直观,实现简单
    ● 缺点:限制太多,性能消耗

  2. 动态组件
    ● 优点:更加灵活,复用性强,拓展性好
    ● 缺点:动态组件的创建和管理增加代码复杂度

const components = [{name: 'menu',input: {props: {...}},output: {changes: () => {...}},component: MyComponent}
]

ng-content 直观而且可以减少组件的嵌套,但是 ng-content 的限制太多,而且在多组件嵌套情况下性能也不好。

不应该有条件地包含带有@if, @for或@switch的。即使该占位符被隐藏,Angular总是实例化并为渲染到占位符的内容创建DOM节点。有关组件内容的条件渲染,请参阅模板片段。

第一次方案决定采用动态组件渲染,即:将组件和配置信息以 Input 形式传入 dropdown 组件,在 dropdown 组件中使用 ViewContainerRef.createComponent 来创建组件。

虽然能实现,但是除了上面提到的代码复杂度以外,还存在几个问题,:

  1. 渲染的时机:要先渲染 dropdown 中的占位元素,然后才能 createComponent,增加了渲染流程的复杂度
  2. 更新数据:因为传入的数据比较复杂,在改变其中数据的时候可能无法触发 Angular 的 OnChange

以上问题导致虽然实现了 dropdown 组件,但是维护难度太大,所以放弃这个方案。

第二次方案
核心要求不变,使用 ngTemplateOutlet 来实现 dropdown 组件。
相比于上面的两个选择,ngTemplateOutlet 实现的代码简洁而且直观。同时它比 ng-content 指向性强,安全性好。

Demo 展示
使用 Angular 19 进行 Demo 的展示。
利用 angular-cli 生成了标准的 angular 项目,插件增加了:@angular/material 和 lodash,其它未作改动
dropdown.html

<div class="my-dropdown-container"><!-- Dropdown Trigger --><div class="my-dropdown-trigger"><ng-container *ngTemplateOutlet="trigger"></ng-container></div><!-- Dropdown Menu --><div class="my-dropdown-menu"><ng-container *ngTemplateOutlet="menu"></ng-container></div>
</div>

dropdown.ts

import { Component, Input, TemplateRef } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';@Component({selector: 'my-dropdown',templateUrl: './myDropdown.component.html',styleUrls: ['./myDropdown.component.less'],imports: [NgTemplateOutlet]
})
export class MyDropdownComponent {@Input({ required: true }) trigger!: TemplateRef<any>;@Input({ required: true }) menu!: TemplateRef<any>;
}

分别实现了三个组件 search-input,dropdown-menu,dropdown-btns。把这些组件封装成了一个标准组件 standard-dropdown

standardDropdown.html

<my-dropdown [trigger]="triggerTmp" [menu]="menuTmp"><ng-template #triggerTmp><dropdown-trigger></dropdown-trigger></ng-template><ng-template #menuTmp><search-input [placeholder]="'Search...'" (valueChange)="handleSearchChange($event)"></search-input><dropdown-menu [options]="options | filterOptions : args" (optionSelected)="handleValuesChange($event)"></dropdown-menu><dropdown-btns (eventClick)="handleEventClick($event)"></dropdown-btns></ng-template>
</my-dropdown>

效果如下:
在这里插入图片描述

dropdown 只负责渲染,在 standard-dropdown 根据需求定制不同的内部组件。search-input,dropdown-menu,dropdown-btns 都是独立的组件,与外部解耦。
我们可以根据不同的需要来创建不同的 dropdown,为它分配不同的功能。如:多选、单选、条件选择、懒加载 等。

验证
这部分比对一开始表述的项目中的问题来看看我们实现的组件是不是能解决问题。

1. 多份 options 备份
我们的 standardDropdown 只维护一个 options,检索的结果直接以 pipe 形式过滤出合法值传入 dropdown-menu。
如果有其它需要按条件检索 options 的情况,将方法封装入 service 中,在使用的时候调用方法过滤 options,而不是提前存储一个处理过的 filteredOptions

2. 多组件复合
ngTemplateOutlet 的方式本身拓展性就很好。每个组件只需要考虑它自己的功能,而与业务有关的代码则放入外层的 standardDropdown 中。那么当我需要增加或者修改组件的时候只要替换它就行了。

3. 复杂的组件嵌套
按这种方式实现的dropdown就像积木一样,我只需要考虑如何拼接它。
不再需要复杂的嵌套,只需要一个外层组件来组合并且处理业务逻辑。而这个外层组件直接控制它内部的小组件,需要什么功能就添加什么组件。


目前来看它比较好的解决了我们一开始提出的问题。

http://www.dtcms.com/a/395387.html

相关文章:

  • Kafka核心架构与高效消息处理指南
  • flink1.18配置多个上游source和下游sink
  • 快速查看自己电脑的ip地址:一个命令见本机私网ip,一步查询本地网络公网ip,附内网ip让外网访问的通用方法
  • 插件化(Plugin)设计模式——Python 的动态导入和参数解析库 argparse 的高级用法
  • 【JavaSE】【网络原理】UDP和TCP原理
  • 高防IP真的能抵御DDoS攻击吗?
  • 93. 复原 IP 地址
  • 智能排班系统,促进人岗匹配提升人效
  • PostgreSQL介绍和PostgreSQL包安装
  • 分享“泰迪杯”数据挖掘挑战赛全新升级——赛题精准对标,搭建 “白名单” 赛事进阶通道
  • 对接文档:快递鸟取件码API,实现物流末端服务自动化
  • GIS学习:GIS认知与开发初步入门
  • 9. NVME与SSD之间的通信
  • Navicat连接PostgreSQL报错:authentication method 10 not supported
  • Diffusion 模型解读
  • 【寰宇光锥舟】 数学模型讨论
  • Further inference in the multiple linear regression model
  • Turtlebot: 开源机器人开发平台 SLAM硬件搭建(激光雷达+IMU+相机+移动底盘)
  • Java 线程的几种状态
  • 在线ps修改图片中的文字
  • Hadoop 保姆级搭建手册:突出教程的细致和易上手
  • 使用gsettings修改命令ubuntu快捷键
  • Linux线程互斥与同步
  • 【AI扣子生成测试用例】自动生成测试用例工作流
  • Hive建表实战
  • Ethernaut Level 5: Token - 整数下溢攻击详解
  • 正向代理 vs 反向代理
  • SNN论文阅读——spikformer
  • 【论文阅读】Robix:机器人交互、推理与规划的统一模型
  • 【论文阅读】AutoDrive-R^2: 激励自动驾驶VLA模型的推理与自我反思能力