国外网站建设什么价格低,青岛网站建设东橙品牌设计,深圳画册设计师,浙江苏省城乡建设厅网站上篇简要介绍了Util在Angular Ts方面的封装情况#xff0c;本文介绍Angular封装的另一个部分#xff0c;即Html的封装。 标准组件与业务组件 对于管理后台这样的表单系统#xff0c;你通常会使用Angular Material或Ng-Zorro这样的UI组件库#xff0c;它们提供了标准化的U… 上篇简要介绍了Util在Angular Ts方面的封装情况本文介绍Angular封装的另一个部分即Html的封装。 标准组件与业务组件 对于管理后台这样的表单系统你通常会使用Angular Material或Ng-Zorro这样的UI组件库它们提供了标准化的UI组件。 标准组件将Ts封装起来以特定标签和属性的方式提供使用。 业务组件使用标准组件拼凑页面并从服务端API获取数据绑定到页面上。 可以看出标准组件是业务开发的基础我们必须将标准组件的开发效率提升到极致。 使用标准组件的问题 直接使用原生标准组件有什么问题呢? 复杂的Html结构 现代流行的UI组件库为了构造美观大气的视觉效果及增强组件的功能特性一个组件需要组装多个Html元素来表达。 在带来美观视觉体验的同时也导致了Html结构变得很复杂。 Angular Material是Google以Material设计风格开发的UI组件库。 我们来看一个Angular Material文本框的例子。 mat-form-fieldinput matInput placeholderFavorite food valueSushi
/mat-form-field 你看到了Angular Material文本框并不是一个input标签input标签嵌套在mat-form-field标签内。 这看上去并不算复杂不过它只是最简单的情况让我们增加两个特性。 mat-form-fieldinput matInput placeholder测试一下 [(ngModel)]value mat-hint哈哈/mat-hintbutton mat-button *ngIfvalue matSuffix mat-icon-button (click)valuemat-iconclose/mat-icon/button
/mat-form-field 我们在文本框的下方添加了提示文本并在文本框右侧加了个按钮你可以点击这个按钮清空文本框的内容。 你应该观察到Html结构变得稍微复杂了让我们再添加两个特性。 mat-form-fieldinput matInput #testControlngModel nametest placeholder金额 [(ngModel)]value required max10span matPrefix$ nbsp;/spanspan matSuffix元/spanmat-hint充值金额/mat-hintbutton mat-button *ngIfvalue matSuffix mat-icon-button (click)valuemat-iconclose/mat-icon/buttonmat-error *ngIftestControl.hasError(max) !testControl.hasError(required)最大金额不能超过10元/mat-errormat-error *ngIftestControl.hasError(required)这是一个必填项/mat-error
/mat-form-field 现在在文本框的左侧加了一个美元符号在文本框右侧添加了后缀“元”另外添加了必填和最大值验证。 这还只是一个不太复杂的文本框Html居然这么长。 组件标签结构成为前端业务开发的第一个关注点。 繁琐的数据绑定 如果要绑定一些可选项到下拉列表一种办法是硬编码。 mat-form-fieldmat-select placeholder请选一个吧mat-option value1A/mat-optionmat-option value2B/mat-optionmat-option value3C/mat-option/mat-select
/mat-form-field 这是具有三个选项的下拉列表。 如果我们要绑定56个民族就需要硬编码56个选项这确实可行不过一个下拉框就60几行占地太广复制粘贴也不方便。 另外下拉选项可能是动态的这些可选值存储在数据库中。 数据绑定大多从服务端获取数据绑定到组件上。 Angular提倡将数据访问与组件分离这个设计理念被Angular Material这些标准组件库所遵循。 为了绑定数据你首先需要发送一个Http请求从服务端获取Json数据转换为Ts对象然后通过Angular提供的循环语法绑定上去。 mat-form-fieldmat-select placeholderFavorite foodmat-option *ngForlet food of foods [value]food.value{{food.viewValue}}/mat-option/mat-select
/mat-form-field Angular Material下拉列表能够分组它与普通下拉列表的Html结构不同如果服务端返回的数据格式不太友好绑定起来将更加困难。 mat-form-fieldmat-select placeholderPokemon [formControl]pokemonControlmat-option-- None --/mat-optionmat-optgroup *ngForlet group of pokemonGroups [label]group.name[disabled]group.disabledmat-option *ngForlet pokemon of group.pokemon [value]pokemon.value{{pokemon.viewValue}}/mat-option/mat-optgroup/mat-select
/mat-form-field 下拉列表并不是唯一需要数据绑定的组件还有一些组件也需要且它们更加复杂比如树型控件表格控件树型表格控件等。 数据绑定成为前端业务开发的第二个关注点。 低效的验证 验证是业务健壮性的基本保障Angular Material表单组件提供了基本的验证方法。 mat-form-fieldinput matInput nametest [(ngModel)]value required
/mat-form-field 上面演示了设置必填项的方法它相当简单只要把required加到input标签上就好了。 遗憾的是文本框虽然得到了验证但却没有显示出任何错误提示消息。 通过添加一个mat-error标签可以显示指定错误提示。 mat-form-fieldinput matInput #controlngModel nametest [(ngModel)]value requiredmat-error *ngIfcontrol.hasError(required)这是一个必填项/mat-error
/mat-form-field 如果组件上有两个验证条件你需要添加两个mat-error标签。 mat-form-fieldinput matInput #controlngModel nametest [(ngModel)]value required max10mat-error *ngIfcontrol.hasError(max) !control.hasError(required)最大值不能超过10/mat-errormat-error *ngIfcontrol.hasError(required)这是一个必填项/mat-error
/mat-form-field 注意为了让提示消息只在特定验证条件失败时才显示你需要在mat-error标签上进行验证状态判断。 如果现在组件包含5个验证条件mat-error和它上面的判断条件将变得相当复杂。 另一方面客户端脚本验证只是为了提升用户体验用户可以绕过界面直接请求你的服务端所以真正的验证必须在服务端完成。 这样一来验证需要在客户端和服务端编写两次这造成了双倍的工作量。 当需求发生变动服务端和客户端的验证很难同步更新维护变得更加困难。 验证成为前端业务开发的第三个关注点。 解决方案 如果开发的时候既不用关心Html的结构又不用关注数据怎么绑定验证还能自动完成甚至连标签和它上面的属性也不用记忆这就最理想不过了该如何实现呢 用Angular组件包装标准组件 首先我们需要用Angular组件对标准组件进行包装以方便功能扩展这个自定义组件称为包装器。 封装Html复杂结构 我们把标准组件的Html标签包装起来以属性的形式提供访问。 mat-form-fieldinput matInput placeholder测试一下 [(ngModel)]value mat-hint哈哈/mat-hintbutton mat-button *ngIfvalue matSuffix mat-icon-button (click)valuemat-iconclose/mat-icon/button
/mat-form-field 把上面标签包装后变成这样。 mat-textbox-wrapperplaceholder测试一下 [(ngModel)]value hint哈哈 showClearButtontrue/mat-textbox-wrapper mat-hint标签现在被转换为hint属性通过showClearButton属性来控制是否显示清空按钮大幅提升了组件的易用性。 约定前后端数据格式 不论下拉列表还是表格甚至树型控件这些需要数据绑定的组件都有一定规律可循。 当你一遍又一遍的复制粘贴仔细观察这个机械乏味的绑定过程不难抽取出公共元素形成前后端数据绑定的通用数据格式。 一旦抽取出前后端通用数据格式你只需将业务数据转换为通用格式发送到客户端就自动绑定完成。 将数据操作内置到包装器 如果你曾经使用过EasyUi这样的组件库定会发现它的数据绑定功能十分强大这是因为它把数据操作内置到了标准组件中。 基于Angular低耦合设计原则Angular Material标准组件并不会直接请求服务端任何数据绑定工作都需要你手工完成不过我们可以将数据操作内置到包装器。 一旦封装完成数据绑定变得非常简单比如设置一个url属性即可服务端返回约定的数据格式。 mat-select-wrapper url/api/test/mat-select-wrapper 用TagHelper封装Angular组件 Angular包装器组件大幅简化了标准组件的使用但它提供的依然是Html而自定义Html标签和属性没有什么提示这意味着你如果记不住这些API就需要随时欣赏API文档。 TagHelper终于闪亮登场。 强类型代码提示和编译时检查 一旦把Html标签封装成TagHelper就可以跟API文档拜拜了把代码提示点出来慢慢选只要你知道该组件确实有这功能哪怕印象有点模糊也没关系。 Html标签和属性的拼写错误也将与你无缘VS大哥会为你把关代码健壮性将大幅提升。 Lambda表达式元数据解析 很多人已经认识到HtmlHelper或TagHelper的好处是强类型提示不过这个认识还很肤浅。 TagHelper真正的威力来自Lambda表达式元数据解析它提供了一个统一的抽象方式自动设置表单组件的常规属性、验证甚至数据绑定。 对于Angular Material表单组件通常需要设置以下常规属性 控件名称 name 占位文本 placeholder 双向绑定 ngModel 常规验证 必填项验证 required Email验证 email 最小长度验证 minlength 最大长度验证 maxlength 最小值验证 min 最大值验证 max 正则验证 pattern 几乎所有表单组件都需要设置这三个常规属性而文本框更需要进行多种验证虽然这些操作并不复杂但由于一个表单界面包含很多组件每个组件都要挨个设置既浪费时间又枯燥乏味。 如果能够自动化设置这些常规属性和验证属性虽然从单个组件看并不起眼但从整个项目的角度能大幅提升生产力。 Lambda表达式元数据解析通过读取C#属性的类型信息以及相关的特性能够自动化设置三大常规属性以及对文本框实施多种验证还解决了客户端与服务端验证无法同步的难题。 一旦用上Lambda表达式界面标签将变得干净整洁你的关注点将迅速转移到业务上。 上面的TagHelper标签生成的结果Html如下。 mat-textbox-wrapper namecode placeholder应用程序编码 requiredMessage应用程序编码不能为空 [(model)]modelmodel.code [maxLength]60 [required]true/mat-textbox-wrapper for指向了ApplicationDto对象的Code属性下面是Code属性的定义。 从Code属性定义可以解析出该组件需要设置的常规属性和验证属性。 上面演示了文本框组件对于单选框多选框下拉框等表单组件都可以使用相同的方式一个for属性基础工作已经完成。 封装的弊端 看了前面的解决方案你知道经过几层高强度封装后组件将变得简单易用不过在将这些方法应用到你的项目之前你需要对这些方法有更深的了解。 任何事物都有其两面性所谓此消彼长在组件变得更加简单易用的同时它的灵活性也在降低。 包装器组件将Html结构封装起来这会导致组件不再支持模板化如果某个功能在你的包装器中未实现那么不能通过在包装器标签内嵌套HTML的方式组合出新的功能。 封装包装器组件有相当多的讲究特别是Angular Material这样的组件库在功能上几乎无法与EasyUi或Ext等企业级UI库相提并论你必须在易用性和灵活性间进行平衡对于像表格这样的重量级组件很难封装到完全满足业务需求这种情况下你必须为其保留模板化能力。 另一方面封装后的傻瓜式TagHelper很容易把程序员惯坏开发常用功能风升水起一碰到超出框架范围的需求就变得束手无策因为他们从来没有学习过原生的知识。 你团队的主力开发人员必须对原生技术有系统了解。 一旦功能超出框架范围你必须有能力扩展框架在必要的时候直接使用原生Html进行开发这时候你更能体会到TagHelper与Html混合编程的好处既提升了常规功能的开发效率又满足了复杂功能对操作体验的需求。 Util组件介绍 下面简要介绍Util中封装的几个常用组件它们来自Angular Material或PrimeNg组件库。 文本框 前面已经展示过文本框的用法除了常规属性设置和验证以外for指向属性的数据类型会影响生成的文本框类型比如属性为日期类型文本框会变成一个日期选择控件。 /// summary/// 创建时间/// /summary[Display( Name 创建时间 )]public DateTime? CreationTime { get; set; } util-textbox forCreationTime/util-textbox 生成的Html结果如下。 mat-datepicker-wrapper namecreationTime placeholder创建时间 [(model)]modelmodel.creationTime/mat-datepicker-wrapper 下面再演示一下数值类型添加了最大和最小值验证并设置前缀文本和后缀图标。 当属性为数值类型时文本框只能输入数字。 /// summary/// 金额/// /summary[Required( ErrorMessage 必须填写金额 )][Range(10,50,ErrorMessage 有效金额在10到50之间)][Display( Name 金额 )]public decimal Money { get; set; } 生成的Html结果如下。 mat-textbox-wrapper typenumber namemoney placeholder金额 [(model)]modelmodel.money startHinta endHintb prefixText$ suffixFontAwesomeIconfa-apple[required]true requiredMessage必须填写金额[min]10 [max]50 minMessage有效金额在10到50之间 maxMessage有效金额在10到50之间/mat-textbox-wrapper 来看看执行效果。 下拉列表 下拉列表的封装重点在于数据绑定。 绑定枚举 下面演示如何把民族枚举绑定到下拉列表。 C#代码如下。 1 /// summary2 /// 民族3 /// /summary4 public enum Nation {5 /// summary6 /// 汉族 7 /// /summary8 [Description( 汉族 )]9 Hz 0,10 /// summary11 /// 蒙古族 12 /// /summary13 [Description( 蒙古族 )]14 Mgz 1,15 /// summary16 /// 回族 17 /// /summary18 [Description( 回族 )]19 HuiZ 2,20 /// summary21 /// 藏族 22 /// /summary23 [Description( 藏族 )]24 Zz 3,25 /// summary26 /// 维吾尔族 27 /// /summary28 [Description( 维吾尔族 )]29 Wwez 4,30 /// summary31 /// 苗族 32 /// /summary33 [Description( 苗族 )]34 Mz 5,35 /// summary36 /// 彝族 37 /// /summary38 [Description( 彝族 )]39 Yz 6,40 /// summary41 /// 壮族 42 /// /summary43 [Description( 壮族 )]44 ZhuangZ 7,45 /// summary46 /// 布依族 47 /// /summary48 [Description( 布依族 )]49 Byz 8,50 /// summary51 /// 朝鲜族 52 /// /summary53 [Description( 朝鲜族 )]54 Cxz 9,55 /// summary56 /// 满族 57 /// /summary58 [Description( 满族 )]59 ManZ 10,60 /// summary61 /// 侗族 62 /// /summary63 [Description( 侗族 )]64 Tz 11,65 /// summary66 /// 瑶族 67 /// /summary68 [Description( 瑶族 )]69 YaoZ 12,70 /// summary71 /// 白族 72 /// /summary73 [Description( 白族 )]74 Bz 13,//baizu75 /// summary76 /// 土家族 77 /// /summary78 [Description( 土家族 )]79 Tjz 14,80 /// summary81 /// 哈尼族 82 /// /summary83 [Description( 哈尼族 )]84 Hnz 15,85 /// summary86 /// 哈萨克族 87 /// /summary88 [Description( 哈萨克族 )]89 Hskz 16,90 /// summary91 /// 傣族 92 /// /summary93 [Description( 傣族 )]94 Dz 17,95 /// summary96 /// 黎族 97 /// /summary98 [Description( 黎族 )]99 Lz 18,
100 /// summary
101 /// 傈僳族
102 /// /summary
103 [Description( 傈僳族 )]
104 Lsz 19,
105 /// summary
106 /// 佤族
107 /// /summary
108 [Description( 佤族 )]
109 Wz 20,
110 /// summary
111 /// 畲族
112 /// /summary
113 [Description( 畲族 )]
114 Sz 21,
115 /// summary
116 /// 高山族
117 /// /summary
118 [Description( 高山族 )]
119 Gsz 22,
120 /// summary
121 /// 拉祜族
122 /// /summary
123 [Description( 拉祜族 )]
124 Lhz 23,
125 /// summary
126 /// 水族
127 /// /summary
128 [Description( 水族 )]
129 ShuiZ 24,
130 /// summary
131 /// 东乡族
132 /// /summary
133 [Description( 东乡族 )]
134 Dxz 25,
135 /// summary
136 /// 纳西族
137 /// /summary
138 [Description( 纳西族 )]
139 Nxz 26,
140 /// summary
141 /// 景颇族
142 /// /summary
143 [Description( 景颇族 )]
144 Jpz 27,
145 /// summary
146 /// 柯尔克孜族
147 /// /summary
148 [Description( 柯尔克孜族 )]
149 Kekzz 28,
150 /// summary
151 /// 土族
152 /// /summary
153 [Description( 土族 )]
154 TuZ 29,
155 /// summary
156 /// 达斡尔族
157 /// /summary
158 [Description( 达斡尔族 )]
159 Dwez 30,
160 /// summary
161 /// 仫佬族
162 /// /summary
163 [Description( 仫佬族 )]
164 Mlz 31,
165 /// summary
166 /// 羌族
167 /// /summary
168 [Description( 羌族 )]
169 Qz 32,
170 /// summary
171 /// 布朗族
172 /// /summary
173 [Description( 布朗族 )]
174 Blz 33,
175 /// summary
176 /// 撒拉族
177 /// /summary
178 [Description( 撒拉族 )]
179 Slz 34,
180 /// summary
181 /// 毛南族
182 /// /summary
183 [Description( 毛南族 )]
184 Mnz 35,
185 /// summary
186 /// 仡佬族
187 /// /summary
188 [Description( 仡佬族 )]
189 Ylz 36,
190 /// summary
191 /// 锡伯族
192 /// /summary
193 [Description( 锡伯族 )]
194 Xbz 37,
195 /// summary
196 /// 阿昌族
197 /// /summary
198 [Description( 阿昌族 )]
199 Acz 38,
200 /// summary
201 /// 普米族
202 /// /summary
203 [Description( 普米族 )]
204 Pmz 39,
205 /// summary
206 /// 塔吉克族
207 /// /summary
208 [Description( 塔吉克族 )]
209 Tjkz 40,
210 /// summary
211 /// 怒族
212 /// /summary
213 [Description( 怒族 )]
214 Nz 41,
215 /// summary
216 /// 乌孜别克族
217 /// /summary
218 [Description( 乌孜别克族 )]
219 Wzbkz 42,
220 /// summary
221 /// 俄罗斯族
222 /// /summary
223 [Description( 俄罗斯族 )]
224 Elsz 43,
225 /// summary
226 /// 鄂温克族
227 /// /summary
228 [Description( 鄂温克族 )]
229 Ewkz 44,
230 /// summary
231 /// 德昂族
232 /// /summary
233 [Description( 德昂族 )]
234 Daz 45,
235 /// summary
236 /// 保安族
237 /// /summary
238 [Description( 保安族 )]
239 Baz 46,
240 /// summary
241 /// 裕固族
242 /// /summary
243 [Description( 裕固族 )]
244 Ygz 47,
245 /// summary
246 /// 京族
247 /// /summary
248 [Description( 京族 )]
249 Jz 48,
250 /// summary
251 /// 塔塔尔族
252 /// /summary
253 [Description( 塔塔尔族 )]
254 Ttrz 49,
255 /// summary
256 /// 独龙族
257 /// /summary
258 [Description( 独龙族 )]
259 Dlz 50,
260 /// summary
261 /// 鄂伦春族
262 /// /summary
263 [Description( 鄂伦春族 )]
264 Elcz 51,
265 /// summary
266 /// 赫哲族
267 /// /summary
268 [Description( 赫哲族 )]
269 Hzz 52,
270 /// summary
271 /// 门巴族
272 /// /summary
273 [Description( 门巴族 )]
274 Mbz 53,
275 /// summary
276 /// 珞巴族
277 /// /summary
278 [Description( 珞巴族 )]
279 Lbz 54,
280 /// summary
281 /// 基诺族
282 /// /summary
283 [Description( 基诺族 )]
284 Jnz 55
285 } 民族枚举 /// summary/// 民族/// /summary[Required( ErrorMessage 必须选择一个民族 )][Display( Name 民族 )][DataMember]public Nation Nation { get; set; } TagHelper代码如下。 util-select forNation/util-select 生成的Html如下可以看出民族可选项被硬编码到Html标签中。 mat-select-wrapper namenation placeholder民族 requiredMessage必须选择一个民族 [(model)]modelmodel.nation[dataSource][{text:汉族,value:0,sortId:0},{text:蒙古族,value:1,sortId:1},{text:回族,value:2,sortId:2},{text:藏族,value:3,sortId:3},{text:维吾尔族,value:4,sortId:4},{text:苗族,value:5,sortId:5},{text:彝族,value:6,sortId:6},{text:壮族,value:7,sortId:7},{text:布依族,value:8,sortId:8},{text:朝鲜族,value:9,sortId:9},{text:满族,value:10,sortId:10},{text:侗族,value:11,sortId:11},{text:瑶族,value:12,sortId:12},{text:白族,value:13,sortId:13},{text:土家族,value:14,sortId:14},{text:哈尼族,value:15,sortId:15},{text:哈萨克族,value:16,sortId:16},{text:傣族,value:17,sortId:17},{text:黎族,value:18,sortId:18},{text:傈僳族,value:19,sortId:19},{text:佤族,value:20,sortId:20},{text:畲族,value:21,sortId:21},{text:高山族,value:22,sortId:22},{text:拉祜族,value:23,sortId:23},{text:水族,value:24,sortId:24},{text:东乡族,value:25,sortId:25},{text:纳西族,value:26,sortId:26},{text:景颇族,value:27,sortId:27},{text:柯尔克孜族,value:28,sortId:28},{text:土族,value:29,sortId:29},{text:达斡尔族,value:30,sortId:30},{text:仫佬族,value:31,sortId:31},{text:羌族,value:32,sortId:32},{text:布朗族,value:33,sortId:33},{text:撒拉族,value:34,sortId:34},{text:毛南族,value:35,sortId:35},{text:仡佬族,value:36,sortId:36},{text:锡伯族,value:37,sortId:37},{text:阿昌族,value:38,sortId:38},{text:普米族,value:39,sortId:39},{text:塔吉克族,value:40,sortId:40},{text:怒族,value:41,sortId:41},{text:乌孜别克族,value:42,sortId:42},{text:俄罗斯族,value:43,sortId:43},{text:鄂温克族,value:44,sortId:44},{text:德昂族,value:45,sortId:45},{text:保安族,value:46,sortId:46},{text:裕固族,value:47,sortId:47},{text:京族,value:48,sortId:48},{text:塔塔尔族,value:49,sortId:49},{text:独龙族,value:50,sortId:50},{text:鄂伦春族,value:51,sortId:51},{text:赫哲族,value:52,sortId:52},{text:门巴族,value:53,sortId:53},{text:珞巴族,value:54,sortId:54},{text:基诺族,value:55,sortId:55}][required]true/mat-select-wrapper 执行效果如下。 绑定服务端数据 为了绑定服务端数据必须约定通用数据格式对于下拉列表服务端C#是由Util.Item来完成的。 1 using System;2 using Newtonsoft.Json;3 4 namespace Util {5 /// summary6 /// 列表项7 /// /summary8 public class Item : IComparableItem {9 /// summary
10 /// 初始化
11 /// /summary
12 /// param nametext文本/param
13 /// param namevalue值/param
14 /// param namesortId排序号/param
15 /// param namegroup组/param
16 /// param namedisabled禁用/param
17 public Item( string text, object value, int? sortId null, string group null, bool? disabled null ) {
18 Text text;
19 Value value;
20 SortId sortId;
21 Group group;
22 Disabled disabled;
23 }
24
25 /// summary
26 /// 文本
27 /// /summary
28 [JsonProperty( text, NullValueHandling NullValueHandling.Ignore )]
29 public string Text { get; }
30
31 /// summary
32 /// 值
33 /// /summary
34 [JsonProperty( value, NullValueHandling NullValueHandling.Ignore )]
35 public object Value { get; }
36
37 /// summary
38 /// 排序号
39 /// /summary
40 [JsonProperty( sortId, NullValueHandling NullValueHandling.Ignore )]
41 public int? SortId { get; }
42
43 /// summary
44 /// 组
45 /// /summary
46 [JsonProperty( group, NullValueHandling NullValueHandling.Ignore )]
47 public string Group { get; }
48
49 /// summary
50 /// 禁用
51 /// /summary
52 [JsonProperty( disabled, NullValueHandling NullValueHandling.Ignore )]
53 public bool? Disabled { get; }
54
55 /// summary
56 /// 比较
57 /// /summary
58 /// param nameother其它列表项/param
59 public int CompareTo( Item other ) {
60 return string.Compare( Text, other.Text, StringComparison.CurrentCulture );
61 }
62 }
63 } Util.Item 客户端Typescript定义了对应的结构。 1 // 列表2 //Copyright 2018 何镇汐3 //Licensed under the MIT license4 //5 import { ISort, sort } from ../core/sort;6 import { util } from ../index;7 8 /**9 * 列表10 */11 export class Select {12 /**13 * 初始化列表14 * param items 列表项集合15 */16 constructor(private items: SelectItem[]) {17 }18 19 /**20 * 转换为下拉列表项集合21 */22 toOptions(): SelectOption[] {23 return this.getSortedItems().map(value new SelectOption(value));24 }25 26 /**27 * 获取已排序的列表项集合28 */29 private getSortedItems() {30 return sort(this.items);31 }32 33 /**34 * 转换为下拉列表组集合35 */36 toGroups(): SelectOptionGroup[] {37 let result: SelectOptionGroup[] new ArraySelectOptionGroup();38 let groups util.helper.groupBy(this.getSortedItems(), t t.group);39 groups.forEach((items, key) {40 result.push(new SelectOptionGroup(key, items.map(item new SelectOption(item)), false));41 });42 return result;43 }44 45 /**46 * 是否列表组47 */48 isGroup(): boolean {49 return this.items.every(value !!value.group);50 }51 }52 53 /**54 * 列表项55 */56 export class SelectItem implements ISort {57 /**58 * 文本59 */60 text: string;61 /**62 * 值63 */64 value;65 /**66 * 禁用67 */68 disabled?: boolean;69 /**70 * 排序号71 */72 sortId?: number;73 /**74 * 组75 */76 group?: string;77 }78 79 /**80 * 下拉列表项81 */82 export class SelectOption {83 /**84 * 文本85 */86 text: string;87 /**88 * 值89 */90 value;91 /**92 * 禁用93 */94 disabled?: boolean;95 96 /**97 * 初始化下拉列表项98 * param item 列表项99 */
100 constructor(item: SelectItem) {
101 this.text item.text;
102 this.value item.value;
103 this.disabled item.disabled;
104 }
105 }
106
107 /**
108 * 下拉列表组
109 */
110 export class SelectOptionGroup {
111 /**
112 * 初始化下拉列表组
113 * param text 文本
114 * param value 值
115 * param disabled 禁用
116 */
117 constructor(public text: string, public value: SelectOption[], public disabled?: boolean) {
118 }
119 } SelectItem 下面来演示一下用法。 先把Nation属性的类型改成int。 /// summary/// 民族/// /summary[Required( ErrorMessage 必须选择一个民族 )][Display( Name 民族 )][DataMember]public int Nation { get; set; } 在WebApi控制器中添加一个方法用来获取民族枚举可选项。 通过Util.Helpers.Enum.GetItems方法可以提取出枚举项列表返回值为ListItem这正是我们约定的标准格式如果返回的是业务类型列表应转换为ListItem。 Success方法用来将ListItem转换为前后端约定的标准结果类型Result。 /// summary/// 获取民族可选项列表/// /summary[HttpGet( nationItems )]public IActionResult GetNationItems() {ListItem items Util.Helpers.Enum.GetItemsUtil.Biz.Enums.Nation();return Success( items );} 再来看TagHelper标签for属性承包了常规的机械工作你将注意力集中在业务上通过手工设置url属性来加载远程数据。 util-select forNation url/api/test/nationItems/util-select 效果跟直接绑定枚举一样不过生成的Html简单很多。 mat-select-wrapper namenation placeholder民族 requiredMessage必须选择一个民族 url/api/application/nationItems [(model)]modelmodel.nation [required]true/mat-select-wrapper 上面演示的下拉列表并未分组我们来改造一下让它以分组显示。 /// summary/// 获取民族可选项列表/// /summary[HttpGet( nationItems )]public IActionResult GetNationItems() {var result Util.Helpers.Enum.GetItemsUtil.Biz.Enums.Nation().GroupBy( t Util.Helpers.String.PinYin( t.Text.Substring( 0,1 ) ) ).SelectMany( t t.ToList().Select( item new Item( item.Text, item.Value, item.SortId, t.Key ) ) );return Success( result );} Util包含大量有用的HelperUtil.Helpers.String.PinYin方法能够将汉字转换为拼音首字母缩写使用GroupBy方法将民族拼音首字母进行分组并转换为Item标准格式。 执行效果如下。 TagHelper没有任何变化Angular Material下拉列表是否分组其原生Html格式完全不同但封装以后你根本感觉不到它们的区别你不需要编写任何一行Ts代码就完成了分组下拉列表的绑定你应该已经体会到封装的强大之处。 单选按钮 单选按钮和下拉列表类似下面演示一下枚举绑定。 C#代码如下。 /// summary/// 性别/// /summarypublic enum Gender {/// summary/// 女/// /summary [Description( 女士 )]Female 1,/// summary/// 男/// /summary[Description( 先生 )]Male 2} /// summary/// 性别/// /summary[Display( Name 性别 )]public Gender Gender { get; set; } TagHelper标签如下。 util-radio forGender/util-radio 生成的Html标签如下。 mat-radio-wrapper label性别 namegender [(model)]modelmodel.gender [dataSource][{text:女士,value:1,sortId:1},{text:先生,value:2,sortId:2}]/mat-radio-wrapper 执行效果如下。 复选框 复选框用于操作布尔类型。 C#代码如下。 /// summary/// 启用/// /summary[Display( Name 启用 )][DataMember]public bool? Enabled { get; set; } TagHelper标签如下。 util-checkbox forEnabled/util-checkbox 生成的Html标签如下。 mat-checkbox namegender [(ngModel)]modelmodel.gender性别/mat-checkbox 执行效果如下。 滑动开关 滑动开关与复选框功能相同但长像更具现代化气质。 TagHelper标签如下。 util-slide-toggle forEnabled/util-slide-toggle 生成的Html标签如下。 mat-slide-toggle nameenabled [(ngModel)]modelmodel.enabled启用/mat-slide-toggle 执行效果如下。 表格 Angular Material表格提供了一套模板化机制你需要任何功能往表格标签中添加元素就好了。 像序号多选分页等常规功能都没有内置到Angular Material表格中Angular Material官网以Demo的形式提供了参考样例如果你直接使用它来进行业务开发将导致十分低效的开发效率。 Util将自动生成序号多选分页排序等常见功能以及数据绑定能力封装到表格包装器组件中同时Util保留了Angular Material表格的模板化能力你依然可以通过往表格标签中添加元素的方式扩展功能。 由于大多表格都需要分页约定的后台数据格式由PagerList承载Ts也定义了类似的分页列表对象。 下面演示一个简单的表格示例。 服务端已经封装了通用的查询方法留待下篇介绍。 先看看TagHelper代码。 1 util-table idtableApplication query-paramqueryParam base-urlapplication2 sortCreationTime sort-directionDesc max-height5003 util-table-column typeCheckbox/util-table-column4 util-table-column typeLineNumber/util-table-column5 util-table-column forCode sorttrue/util-table-column6 util-table-column forName sorttrue/util-table-column7 util-table-column forEnabled sorttrue/util-table-column8 util-table-column forRegisterEnabled sorttrue/util-table-column9 util-table-column forCreationTime sorttrue/util-table-column
10 util-table-column title操作 columnoperation
11 util-table-cell
12 util-a stylesIcon tooltip编辑 bind-link[update,row.id]
13 util-icon material-iconEdit/util-icon
14 /util-a
15 util-button stylesIcon menu-idmenu
16 util-icon material-iconMore_Vert/util-icon
17 util-menu idmenu
18 util-menu-item label删除 material-iconDelete on-clickdelete(row.id)/util-menu-item
19 util-menu-item label查看详细 material-iconVisibility bind-link[detail,row.id]/util-menu-item
20 /util-menu
21 /util-button
22 /util-table-cell
23 /util-table-column
24 /util-table 生成的Html如下。 1 mat-table-wrapper #tableApplication baseUrlapplication keyapplication maxHeight500 [(queryParam)]queryParammat-table matSort matSortActiveCreationTime matSortDirectiondesc matSortDisableClear [dataSource]tableApplication.dataSource [style.max-height]tableApplication.maxHeight?tableApplication.maxHeightpx:null [style.min-height]tableApplication.minHeight?tableApplication.minHeightpx:null2 ng-container matColumnDefselectCheckboxmat-header-cell *matHeaderCellDefmat-checkbox (change)$event?tableApplication.masterToggle():null [checked]tableApplication.isMasterChecked() [disabled]!tableApplication.dataSource.data.length [indeterminate]tableApplication.isMasterIndeterminate()/mat-checkbox/mat-header-cellmat-cell *matCellDeflet rowmat-checkbox (change)$event?tableApplication.checkedSelection.toggle(row):null (click)$event.stopPropagation() [checked]tableApplication.checkedSelection.isSelected(row)/mat-checkbox/mat-cell/ng-container3 ng-container matColumnDeflineNumbermat-header-cell *matHeaderCellDefID/mat-header-cellmat-cell *matCellDeflet row{{ row.lineNumber }}/mat-cell/ng-container4 ng-container matColumnDefcodemat-header-cell *matHeaderCellDef mat-sort-header应用程序编码/mat-header-cellmat-cell *matCellDeflet row{{ row.code }}/mat-cell/ng-container5 ng-container matColumnDefnamemat-header-cell *matHeaderCellDef mat-sort-header应用程序名称/mat-header-cellmat-cell *matCellDeflet row{{ row.name }}/mat-cell/ng-container6 ng-container matColumnDefenabledmat-header-cell *matHeaderCellDef mat-sort-header启用/mat-header-cellmat-cell *matCellDeflet rowmat-icon *ngIfrow.enabledcheck/mat-iconmat-icon *ngIf!row.enabledclear/mat-icon/mat-cell/ng-container7 ng-container matColumnDefregisterEnabledmat-header-cell *matHeaderCellDef mat-sort-header启用注册/mat-header-cellmat-cell *matCellDeflet rowmat-icon *ngIfrow.registerEnabledcheck/mat-iconmat-icon *ngIf!row.registerEnabledclear/mat-icon/mat-cell/ng-container8 ng-container matColumnDefcreationTimemat-header-cell *matHeaderCellDef mat-sort-header创建时间/mat-header-cellmat-cell *matCellDeflet row{{ row.creationTime | date:yyyy-MM-dd }}/mat-cell/ng-container9 ng-container matColumnDefoperationmat-header-cell *matHeaderCellDef操作/mat-header-cell
10 mat-cell *matCellDeflet row
11 a mat-icon-button matTooltip编辑 [routerLink][update,row.id]
12 mat-iconedit/mat-icon
13 /a
14 button mat-icon-button typebutton [matMenuTriggerFor]menu
15 mat-iconmore_vert/mat-icon
16 mat-menu #menumatMenung-template matMenuContent
17 button (click)delete(row.id) mat-menu-itemmat-icondelete/mat-iconspan删除/span/button
18 button mat-menu-item [routerLink][detail,row.id]mat-iconvisibility/mat-iconspan查看详细/span/button
19 /ng-template/mat-menu
20 /button
21 /mat-cell
22 /ng-container
23 mat-header-row *matHeaderRowDef[selectCheckbox,lineNumber,code,name,enabled,registerEnabled,creationTime,operation];sticky:true/mat-header-rowmat-row (click)tableApplication.selectedSelection.select(row) *matRowDeflet row;columns:[selectCheckbox,lineNumber,code,name,enabled,registerEnabled,creationTime,operation] classmat-row-hover [class.selected]tableApplication.selectedSelection.isSelected(row)/mat-row/mat-table/mat-table-wrapper 可以看见Html比TagHelper代码要复杂得多这还是封装过后的情况如果完全没有封装折腾一个表格将会耗费你大量精力且Bug遍地难以维护。 Ts代码几乎看不见你只需设置base-url属性数据绑定就完成了。 base-url是一个基地址根据约定创建服务端请求地址/api/baseUrl,如果你的请求地址不同可以改为设置url属性。 执行效果如下。 树型表格 树型层次关系是业务常见操作之一。 Util Angular Material的封装主要是在Angular Material 5.x之前完成的Angular Material 6.x才提供了树型控件所以Util尚未封装树型控件不过为了解决编辑树型层次困难的局面我从PrimeNg组件库Copy了一个树型表格过来。 PrimeNg是另一个开源的Angular组件库它的树型表格功能非常弱我花了数天时间来修改它的源码以满足我的基本需求。 由于树型包含同步加载异步加载上移下移单选多选等操作封装树型表格比普通表格要复杂得多。 服务端提供了PrimeTreeControllerBase和PrimeTreeNode等对象类型来实现与客户端通信不过它们都还相当具体化待后续封装Ng-Zorro树型组件时再来重构。 一旦封装完成它用起来就跟Angular Material表格几乎没什么区别。 来看个示例。 TagHelper代码如下。 util-tree-table idtreeTable_role base-urlrole query-paramqueryParam selection-modeMultiple keytreeTable_role util-tree-table-column forName/util-tree-table-columnutil-tree-table-column forCode/util-tree-table-columnutil-tree-table-column forEnabled/util-tree-table-columnutil-tree-table-column forSortId/util-tree-table-columnutil-tree-table-column title操作util-a stylesIcon tooltip添加下级角色 linkcreate query-params{id:row.data.id}util-icon material-iconAdd/util-icon/util-autil-button idbtnMoveUp stylesIcon tooltip上移 ng-if!isFirst(row) on-clickmoveUp(row,btnMoveUp,$event)util-icon material-iconArrow_Upward/util-icon/util-buttonutil-button idbtnMoveDown stylesIcon tooltip下移 ng-if!isLast(row) on-clickmoveDown(row, btnMoveDown,$event)util-icon material-iconArrow_Downward/util-icon/util-buttonutil-button stylesIcon menu-idmenu on-clickselectRow(row,$event)util-icon material-iconMore_Vert/util-iconutil-menu idmenuutil-menu-item label编辑 material-iconEdit bind-link[update,row.data.id]/util-menu-itemutil-menu-item label禁用 material-iconLock on-clickdisable(row)/util-menu-itemutil-menu-item label启用 material-iconLock_Open on-clickenable(row)/util-menu-itemutil-menu-item label删除 material-iconDelete on-clickdelete(row)/util-menu-itemutil-menu-item label详细 material-iconVisibility bind-link[detail,row.data.id]/util-menu-item/util-menu/util-button/util-tree-table-column
/util-tree-table 生成的Html如下。 p-tree-table #treeTable_role baseUrlrole keytreeTable_role selectionModecheckbox [(queryParam)]queryParamp-column fieldname header角色名称/p-columnp-column fieldcode header角色编码/p-columnp-column fieldenabled header启用ng-template let-firstfirst let-iindex let-lastlast let-rowrowDatamat-icon *ngIfrow.data.enabledcheck/mat-iconmat-icon *ngIf!row.data.enabledclear/mat-icon/ng-template/p-columnp-column fieldsortId header排序号/p-columnp-column header操作ng-template let-firstfirst let-iindex let-lastlast let-rowrowDataa mat-icon-button matTooltip添加下级角色 routerLinkcreate [queryParams]{id:row.data.id}mat-iconadd/mat-icon/amat-button-wrapper #btnMoveUp (onClick)moveUp(row,btnMoveUp,$event) *ngIf!isFirst(row) stylemat-icon-button tooltip上移ng-templatemat-iconarrow_upward/mat-icon/ng-template/mat-button-wrappermat-button-wrapper #btnMoveDown (onClick)moveDown(row, btnMoveDown,$event) *ngIf!isLast(row) stylemat-icon-button tooltip下移ng-templatemat-iconarrow_downward/mat-icon/ng-template/mat-button-wrapperbutton (click)selectRow(row,$event) mat-icon-button typebutton [matMenuTriggerFor]menumat-iconmore_vert/mat-iconmat-menu #menumatMenung-template matMenuContentbutton mat-menu-item [routerLink][update,row.data.id]mat-iconedit/mat-iconspan编辑/span/buttonbutton (click)disable(row) mat-menu-itemmat-iconlock/mat-iconspan禁用/span/buttonbutton (click)enable(row) mat-menu-itemmat-iconlock_open/mat-iconspan启用/span/buttonbutton (click)delete(row) mat-menu-itemmat-icondelete/mat-iconspan删除/span/buttonbutton mat-menu-item [routerLink][detail,row.data.id]mat-iconvisibility/mat-iconspan详细/span/button/ng-template/mat-menu/button/ng-template/p-column
/p-tree-table 看上去Html比TagHelper没有复杂多少那是因为已经将功能内置到树型表格组件内部不得不承认有时候修改源码比在外围扩展要省很多力气。 执行效果如下。 在完成了异步加载多选上移下移搜索删除行刷新分页等一系列功能后一行Ts都没有是否感觉到很清爽呢。 其它组件 Util还封装了颜色拾取器菜单侧边栏等组件限于篇幅就不一一介绍。 小结 本文简要介绍了Angular标准组件的封装手法它能够大幅提升业务开发的生产力同时也提醒你必须系统学习原生技术否则碰上稍微复杂点的问题就无法解决。 本文更多的是介绍封装思路而封装思想与具体UI技术无关一旦你了解了封装背后的动机和技巧不论Angular还是Vue或者Android组件甚至小程序都可以通过封装来提升开发效率。 未完待续C#服务端CRUD的封装将在下篇介绍。 写文需要动力请大家多多支持点下推荐Github点下星星。 Util应用框架交流一群: 24791014已满 Util应用框架交流二群: 184097033 Util应用框架地址https://github.com/dotnetcore/util转载于:https://www.cnblogs.com/xiadao521/p/Util-Introduction-5.html