angular select+input 实现动态加载选项,动态输入搜索

本文介绍如何在Angular应用中结合select和input实现动态加载选项及搜索功能。当用户输入内容后,首先在现有选项中过滤,若无匹配项则向后台发起请求。在使用过程中,遇到的问题包括延迟加载导致的第二次点击才显示正确选项,以及点击过快导致的选项混乱。解决方案包括在请求前清空选项值,并通过自动触发输入框聚焦来即时刷新数据。

先看效果:

从后台查到有数据就会展示,一点击清空内容,根据大类去查询相应品牌,并且可以选择

输入内容后会先在当前的选项中过滤,如果没有的话会向后台发请求查询

这是组件代码

export interface SearchOption {
    placeholder?: string;
    searchId: string; // 控件的标识id,用于区分多个select控件
    optionId: string; // 绑定值的id
    optionField: string; // input框展示内容
    asyncLoading?: boolean;
    needInput?: boolean; // 组件为typeahead+select,如果设置为true,代表需要input框中的value,且该value不属于任何一个option
}

@Component({
    selector: 'search-select',
    template: `
        <input [(ngModel)]="selected"
               [typeahead]="dataSource"
               (typeaheadOnSelect)="searchOnSelect($event)"
               [typeaheadMinLength]="0"
               [typeaheadOptionsLimit]="500"
               (blur)="blurOnSelect($event)"
               (focus)="focusOnSelect($event)"
               (input)="keyupOnSelect($event)"
               [typeaheadScrollable]="true"
               typeaheadOptionField="{{searchOption.optionField}}"
               placeholder="{{searchOption.placeholder}}"
               class="form-control"
               id="select"
               >
    `
})
export class SearchSelectComponent {
    @Input() showItems: any[];
    @Input() selected: string;//初始化默认值
    @Input() searchOption: SearchOption;
    @Input() keepValue:boolean;
    @Input() itemInfo:any;//当前选中的节点
    @Output('onSearchOnSelect') onSearchOnSelect = new EventEmitter<any>();
    @Output('asyncLoadFunction') asyncLoadFunction = new EventEmitter<any>();
    @Output('onKeyupOnSelect') onKeyupOnSelect = new EventEmitter<any>();
    lastTimeSelected: string;
    tmpSelected:string;
    dataSource: Observable<any>;

    constructor() {
        this.dataSource = Observable.create((observer: any) => {
            observer.next(this.selected);
        }).mergeMap((token: string) => this.getSearchObservable(token));
    }
    ngOnInit(){
        if(this.keepValue){
            this.tmpSelected = this.selected;
        }
    }
    
    getSearchObservable(token: string): Observable<any> {
        if (this.searchOption.asyncLoading) {
            this.asyncLoadFunction.emit(token);
        }
        return this.checkObservableOption(token);
    }

    searchOnSelect(e: TypeaheadMatch): void {
        this.selected = e.value;
        this.lastTimeSelected = e.value;
        const item = {
            key: this.searchOption.searchId,
            selected: e
        };
        this.onSearchOnSelect.emit(item);
    }

    checkObservableOption(token: string): Observable<any> {
        return Observable.of(
            this.showItems.filter((state: any) => {
                return state[this.searchOption.optionField].toLowerCase().indexOf(token) > -1;
            })
        );
    }

    /**
     *  未选择数据之前为清空状态
     * @param e
     */
    blurOnSelect(e): void {
        if (!this.searchOption.needInput) {
            // 大部分的控件使用这种模式,保持向后兼容性,不会修改控件行为
            this.emitSelection();
        }
         else {
            // 新添加的行为,除了保持之前的模式,还支持获取控件中输入的值
            this.emitInputOrSelection();
        }
    }
    private emitSelection() {
        if (this.selected) {
            this.selected = this.lastTimeSelected;
        }
        else {
            this.handleEmptySelection();
        }
    }

    private emitInputOrSelection() {
        if (this.selected && this.showItems.find(item => item === this.selected)) {
            this.selected = this.lastTimeSelected;
        } else if (this.selected && !this.showItems.find(item => item === this.selected)) {
            const item = {
                key: this.searchOption.searchId,
                input: this.selected
            };
            this.onSearchOnSelect.emit(item);
        }else if(this.keepValue&&!this.selected){
            this.selected = this.tmpSelected
        }
         else {
            this.handleEmptySelection();
        }
    }

    private handleEmptySelection() {
        this.selected = '';
        this.lastTimeSelected = '';
        const item = {
            key: this.searchOption.searchId,
            selected: ''
        };
        this.onSearchOnSelect.emit(item);
    }

    /**
     *  选择数据之前清空
     */
    focusOnSelect(e): void {
        this.selected = '';
        if(this.itemInfo){
            this.asyncLoadFunction.emit({itemInfo: this.itemInfo});
        }
    }
   keyupOnSelect(e): void {
       const item = {
           key: $("#select").val(),
           keycode: e.keyCode
       }
        this.onKeyupOnSelect.emit(item)
    }
}

根据需要修改过组件,比如需要留下input的值,加了个keepValue,总之根据需要添加input和output。这是使用时的html

        <td>
            <search-select
            id="brand{{idx}}"
            [searchOption]="asyncBrandOption"
            [showItems]="showBrand"
            [keepValue]=true
            [selected]="purchaseTable.materialModelBrand"
            [itemInfo]="purchaseTable"
            (asyncLoadFunction)="asyncBrandFunction(purchaseTable,idx)"
            (onSearchOnSelect)="onSearchOnSelect($event)"
            (onKeyupOnSelect)="onKeySelectBrand($event)">
            </search-select>
       </td>

使用时在ts中定义相应的函数,比如asyncBrandFunction,接收到组件传的值,再发请求得到动态的数据,赋值给showitems就可以了。

上面说完了大致思想,下面说遇到的问题。

 

由于点击后发请求,有个延迟问题,在第二次点击的时候数据才能请求回来,并且点的太快,会显示上一次的选项,对于页面中输入框多的情况,点击后选项会乱。

选项会乱这个问题很好解决,只要在发请求之前清空上次的选项值就可以了。

第二次点击才能出来这个问题,最后有一种思路,就是我们让程序自动点击一次当前的输入框

代码如下:

如果你仔细看会看到上面的循环把index赋值给了idx。

   $('#brand'+idx+' input').click();

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值