基于 Angular和Material autocomplete组件再封装的可双向绑定key-value的可输入下拉框
GitHub: https://github.com/Xinzheng-Li/AngularCustomerComponent
效果图:为了方便使用,把许多比如ADD的功能去了,可以在使用后自行实现。
调用:
1 <app-autocomplete-input [menuItems]="autocompleteInputData" [(model)]="autocompleteInputModel" [showAddBtn]="true"
2 [(value)]="autocompleteInputValue" (objectChange)="onChange($event)" (focus)="onFocus($event)"
3 (input)="onInput($event)" (change)="onModelChange($event)" (blur)="onBlur($event)"
4 #autocompleteInput></app-autocomplete-input>
前端:
1 <div>
2 <input type="text" matInput [formControl]="myControl"
3 #autocompleteTrigger="matAutocompleteTrigger" [matAutocomplete]="auto" [placeholder]="placeholder"
4 #autocompleteInput maxlength={{maxlength}} (focus)="onFocus($event)" (input)="onInput($event)"
5 (change)="onModelChange($event)" (blur)="onBlur($event)">
6
7 <mat-autocomplete #auto="matAutocomplete" #autocomplete isDisabled="true" (optionSelected)="selectedOption($event)"
8 [displayWith]="displayFn">
9 <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
10 {{option.label}}
11 </mat-option>
12 <mat-option *ngIf="loading" [disabled]="true" class="loading">
13 loading...
14 </mat-option>
15 <mat-option *ngIf="showAddBtn&&inputText!=''" [ngClass]="{'addoption-active':addoptionActive}"
16 [disabled]="!addoptionActive" value="(add)" class="addoption">
17 + Add <span>{{ inputText?'"'+inputText+'"':inputText }}</span>
18 </mat-option>
19 </mat-autocomplete>
20 </div>
后台:
1 import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
2 import { FormControl } from '@angular/forms';
3 import { MatAutocomplete, MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete'
4 import { Observable, Subject, debounceTime, map, startWith } from 'rxjs';
5
6 interface Menu {
7 value: any;
8 label: string;
9 }
10
11 @Component({
12 selector: 'app-autocomplete-input',
13 templateUrl: './autocomplete-input.component.html',
14 styleUrls: ['./autocomplete-input.component.scss']
15 })
16 export class AutocompleteInputComponent implements OnInit {
17 @Input() disabled = false;
18 @Input() disabledInput = false;
19 @Input() placeholder = 'autocompleteInput';
20 @Input() maxlength: number = 50;
21 @Input() showAddBtn = false;
22 @Input() loading = false;
23 _menuItems!: Menu[];
24 @Input()
25 get menuItems() {
26 return this._menuItems;
27 }
28 set menuItems(val) {
29 this._menuItems = val;
30 if (this.model) {
31 let mapItem = this.menuItems.find((x) => x.label?.toLowerCase().trim() == this.model?.trim()?.toLowerCase());
32 if (mapItem) {
33 this.value = mapItem.value;
34 } else {
35 this.model = this.value = '';
36 }
37 }
38 this.myControl.setValue(this.model ?? '');
39 }
40
41 modelValue: any = { name: '', value: '' };
42 @Output() objectChange = new EventEmitter();
43
44 //Only for binding model
45 @Output() modelChange = new EventEmitter();
46 @Input()
47 get model() {
48 return this.modelValue?.name?.trim() ?? '';
49 }
50 set model(val) {
51 this.modelValue.name = this.inputText = val?.trim();
52 this.modelChange.emit(this.modelValue.name);
53 this.inputChangeSubject.next(this.modelValue.name);
54 }
55
56 @Output() valueChange = new EventEmitter();
57 @Input()
58 get value() {
59 return this.modelValue.value;
60 }
61 set value(val) {
62 this.modelValue.value = val;
63 this.valueChange.emit(this.modelValue.value);
64 }
65
66 @Output() inputChange = new EventEmitter<any>();
67
68 myControl = new FormControl<string | any>('');
69 filteredOptions!: Observable<any[]>;
70 @ViewChild('autocompleteInput') autocompleteInput: any;
71 @ViewChild('autocomplete') autocomplete!: MatAutocomplete;
72 @ViewChild(MatAutocompleteTrigger) autocompleteTrigger!: MatAutocompleteTrigger;
73
74 ngOnInit(): void {
75 this.filteredOptions = this.myControl.valueChanges.pipe(
76 startWith(''),
77 map((value) => {
78 const name = typeof value === 'string' ? value : value.label;
79 return name ? this._filter(name as string) : this.menuItems.slice();
80 })
81 );
82 this.registEventSubject();
83 this.inputText = '';
84 }
85
86 ngOnChanges(changes: SimpleChanges) {
87 if (changes['menuItems'] && !changes['menuItems'].firstChange) this.loading = false;
88 if (changes['disabled']) {
89 this.disabled ? this.myControl.disable() : this.myControl.enable();
90 }
91 if (changes['value']) {
92 let item = this.menuItems.find((x) => x.value == changes['value'].currentValue);
93 if (item) {
94 this.value = item?.value ?? '';
95 this.model = item?.label ?? '';
96 }
97 }
98 if (changes['model']) {
99 this.inputText = changes['model'].currentValue ?? '';
100 this.myControl.setValue(this.model ?? '');
101 }
102 }
103
104 private inputChangeSubject = new Subject<string>();
105 private registEventSubject() {
106 this.inputChangeSubject.pipe(debounceTime(100)).subscribe((data: any) => {
107 if (this.loading) return;
108 if (this.autocompleteInput?.nativeElement) this.autocompleteInput.nativeElement.value = this.model;
109 this.objectChange.emit(this.modelValue);
110 });
111 }
112
113 private _filter(item: any): any[] {
114 const filterValue = item?.toLowerCase()?.trim();
115 return this.menuItems.filter((option) => option.label.toLowerCase().includes(filterValue));
116 }
117
118 displayFn(e: any) {
119 return e && e.label ? e.label : '';
120 }
121 onFocus(e: any) {
122 if (this.disabledInput) e.target.blur();
123 }
124 @Output() blur = new EventEmitter<any>();
125 onBlur(e: any) {
126 if (e.currentTarget.value != this.model) {
127 this.inputChangeSubject.next(this.model);
128 } else {
129 this.blur.emit(e);
130 }
131 }
132
133 inputText = '';
134 addoptionActive = false;
135 onInput(e: any) {
136 if (e.currentTarget.value == '') {
137 this.addoptionAction(false);
138 this.myControl.setValue('');
139 } else if (this.menuItems.find((x) => x.label.toLowerCase() == e.currentTarget.value?.trim()?.toLowerCase())) {
140 this.addoptionAction(false);
141 } else {
142 this.addoptionAction(true);
143 }
144 this.inputText = e.currentTarget.value;
145 e.currentTarget.value = this.inputText = e.currentTarget.value.replaceAll(/[`\\~!@#$%^\*_\+={}\[\]\|;"<>\?]/gi, '');
146 if (e.currentTarget.value?.trim() == '') this.myControl.setValue(e.currentTarget.value);
147 this.inputChange.emit(e);
148 }
149
150 onModelChange(e: any) {
151 if (this.loading) return;
152 if (e.currentTarget.value?.trim()) {
153 let mapItem = this.menuItems.find(
154 (x) => x.label.toLowerCase().trim() == e.currentTarget.value?.trim()?.toLowerCase()
155 );
156 if (mapItem) {
157 this.model = e.currentTarget.value = mapItem.label;
158 this.value = mapItem.value;
159 } else {
160 this.model = e.currentTarget.value;
161 this.value = '';
162 }
163 } else {
164 this.model = this.inputText = e.currentTarget.value;
165 this.value = '';
166 }
167 }
168
169 selectedOption(e: any) {
170 if (typeof e.option.value === 'string') {
171 this.autocompleteInput.nativeElement.value = this.inputText;
172 } else {
173 let mod = e.option.getLabel() ?? '';
174 let val = e.option.value?.value ?? '';
175 if (val != this.value || mod != this.model) {
176 this.model = mod ?? '';
177 this.value = val ?? '';
178 }
179 if (this.value && this.model) {
180 this.addoptionActive = false;
181 }
182 }
183 }
184
185 panelAction(type: number) {
186 type == 1 ? this.autocompleteTrigger.openPanel() : this.autocompleteTrigger.closePanel();
187 }
188
189 addoptionAction(type: boolean) {
190 this.addoptionActive = type;
191 }
192
193 //It will trigger the change event of the model!
194 clearText() {
195 this.value = this.model = '';
196 }
197 }
实现逻辑:
原Material的autocomplete控件将下拉框和输入内容分为不同的事件,并且无法自定义下拉选项,像例子中的ADD功能,如果使用原控件,则会将“+ Add XXX”显示到输入框中。
另外就是原控件仅支持显示值绑定,因为输入框是没有key的,故,将输入框和下拉框进行二次封装,实现key-value的双向绑定和自定义选项的功能。
必传参数:
106行:防抖函数0.1秒是因为选择项后会触发两次Change事件(selelctoption+modelChange)
基于 Angular和Material autocomplete组件再封装的可双向绑定key-value的可输入下拉框的更多相关文章
- 从后台绑定数据到ligerui 的comboBox下拉框组件
这次来记录一下ligerUI的comboBox下拉框组件,ligerUI的API里也有相关描写叙述,上面都是前台写死数据,然后显示在组件中,我这次要说的是将后台的数据绑定到下拉框组件中,废话不多说. ...
- 基于element-ui的多选下拉框和tag标签的二次封装
前言: 今年这大半年我主要负责公司的后台教务管理的开发,这个管理系统目前主要是给公司的内部人员去配置公司的核心项目(例如:熊猫小课)的所有数据,例如课程的配置.课程期数的配置.课程版本的配置.活动的配 ...
- 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用
在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...
- 手把手教学~基于element封装tree树状下拉框
在日常项目开发中,树状下拉框的需求还是比较常见的,但是element并没有这种组件以供使用.在这里,小编就基于element如何封装一个树状下拉框做个详细的介绍. 通过这篇文章,你可以了解学习到一个树 ...
- 基于Bootstrap的下拉框插件bootstrap-select
写在前面: 在这次的项目中,没有再使用liger-ui做为前端框架了,改为了Bootstrap,这次也好接触下新的技术,在学习的过程中发现,Bootstrap的一些组件基本都是采用class的形式,就 ...
- 自定义Angular指令与jQuery实现的Bootstrap风格数据双向绑定的单选&多选下拉框
先说点闲话,熟悉Angular的猿们会喜欢这个插件的. 00.本末倒置 不得不承认我是一个喜欢本末倒置的人,学生时代就喜欢先把晚交的作业先做,留着马上就要交的作业不做,然后慢悠悠做完不重要的作业,卧槽 ...
- Combo( 自定义下拉框) 组件
本节课重点了解 EasyUI 中 Combo(自定义下拉框)组件的使用方法,这个组件依赖于ValidateBox(验证框)组件 一. 加载方式自定义下拉框不能通过标签的方式进行创建.<input ...
- 第二百一十二节,jQuery EasyUI,Combo(自定义下拉框)组件
jQuery EasyUI,Combo(自定义下拉框)组件 学习要点: 1.加载方式 2.属性列表 3.事件列表 4.方法列表 本节课重点了解 EasyUI 中 Combo(自定义下拉框)组件的使用方 ...
- vue2.X props 数据传递 实现组件内数据与组件外的数据的双向绑定
vue2.0 禁止 子组件修改父组件数据 在Vue2中组件的props的数据流动改为了只能单向流动,即只能由组件外(调用组件方)通过组件的DOM属性attribute传递props给组件内,组件内只能 ...
- chosen组件实现下拉框
chosen组件用于增强原生的select控件,使之有更好的用户体验.官方demo https://harvesthq.github.io/chosen/ 目前项目中碰到的使用,比如一个页面中有两个不 ...
随机推荐
- 前端vue uni-app列表组件 list组件,简单好用
快速实现uni-app列表组件 list组件,简单好用; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12675 效果图如下: ...
- 2. Tomcat-Servlet
1. Tomcat 目录结构说明: bin 可执行文件目录 conf 配置文件目录 lib 存放 lib 的目录 logs 日志文件目录 webapps 项目部署的目录 work 工作目 ...
- (一)centos7下如何搭建Nginx和FastDFS文件管理-环境搭建
一.关于FastDFS 1.FastDFS简介 FastDFS(Fast Distributed File System)是一个开源的分布式文件系统,旨在解决大规模文件存储和访问的问题,例如图片.音视 ...
- .NET Core 3.1使用docker打包并部署
目录 简介 环境介绍 开发环境 部署环境 编写Dockerfile文件 生成Docker镜像 运行容器 访问接口 结语 简介 本文主要说明使用.NET Core 3.1搭建的站点如何使用docker打 ...
- Django message组件
使用message组件要在seetings中配置 ①INSTALLED_APPS (项目需要什么功能都放在这 既可以有数据库,又可以写代码,html文件,和自己写的APP一个道理) ②MIDDLE ...
- 可托拉拽的WPF选项卡控件,强大好用!
推荐一个简单易用的WPF选项卡控件. 项目简介 这是一个基于WPF开发的,可扩展.高度可定制.轻量级的UI组件,支持拖拉拽功能,可以让开发人员快速实现需要选项卡窗口的系统. 特色功能 1.拖拉拽标签: ...
- JFrame一些基础小知识
JFrame.setLocationRelativeTo方法 JFrame.setLocationRelativeTo()是一个Java Swing中的方法,它用于将窗口居中显示在屏幕上. 当你调用该 ...
- Builder 生成器模式简介与 C# 示例【创建型2】【设计模式来了_2】
〇.简介 1.什么是生成器模式? 一句话解释: 在构造一个复杂的对象(参数多且有可空类型)时,通过一个统一的构造链路,可选择的配置所需属性值,灵活实现可复用的构造过程. 生成器模式的重心,在于分离 ...
- quarkus依赖注入之六:发布和消费事件
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus依赖注入> ...
- Java源代码是如何编译,加载到内存中的?
1.前言 相信许多开发同学看过<深入理解java虚拟机>,也阅读过java虚拟机规范,书籍和文档给人的感觉不够直观,本文从一个简单的例子来看看jvm是如何工作的吧. 本文所有操作均在mac ...