Angular Material 6 中用于自动完成的无限滚动

2023-11-30

我正在尝试在 Angular Material 6 中实现自动完成的无限滚动。我的场景很简单,我有一个启用了自动完成功能的输入字段。当用户键入时,我将使用输入字段中的文本进行 HTTP 调用,以将结果显示为建议。但我只想显示 25 条建议,如果当用户滚动到底部时结果计数超过 25 条,我想再添加 25 条。 喜欢this角度 2

我在网上找不到。

请给我建议或帮助我。先感谢您。

<mat-form-field>
  <input matInput placeholder="Experiment Name" formControlName="experimentName" [matAutocomplete]="expNamesAutocomplete">
</mat-form-field>
<mat-autocomplete #expNamesAutocomplete="matAutocomplete">
  <mat-option *ngFor="let option of suggestedExpNames" [value]="option">
              {{ option }}
  </mat-option>
</mat-autocomplete>

我知道这篇文章已经过时了,但我将解决方案留在这里,以防有人需要。

诀窍是获取对 mat-autocomplete 面板滚动条的引用。我已经使用自定义指令完成了此操作:

import { Directive, ElementRef, EventEmitter, Input, Output, Host, Self, Optional, AfterViewInit, OnDestroy } from '@angular/core';
import { MatAutocomplete } from '@angular/material';
import { Observable, fromEvent, of, Subject, merge, combineLatest } from 'rxjs';
import { map, startWith, switchMap, tap, debounceTime, filter, scan, withLatestFrom, mergeMap, takeUntil, takeWhile, distinctUntilChanged, skipUntil, exhaustMap, endWith } from 'rxjs/operators';
import { takeWhileInclusive } from 'rxjs-take-while-inclusive';

export interface IAutoCompleteScrollEvent {
  autoComplete: MatAutocomplete;
  scrollEvent: Event;
}

@Directive({
  selector: 'mat-autocomplete[optionsScroll]'
})
export class OptionsScrollDirective implements OnDestroy {

  @Input() thresholdPercent = .8;
  @Output('optionsScroll') scroll = new EventEmitter<IAutoCompleteScrollEvent>();
  _onDestroy = new Subject();

  constructor(public autoComplete: MatAutocomplete) {
    this.autoComplete.opened.pipe(
      tap(() => {
        // Note: When autocomplete raises opened, panel is not yet created (by Overlay)
        // Note: The panel will be available on next tick
        // Note: The panel wil NOT open if there are no options to display
        setTimeout(() => {
          // Note: remove listner just for safety, in case the close event is skipped.
          this.removeScrollEventListener();
          this.autoComplete.panel.nativeElement
            .addEventListener('scroll', this.onScroll.bind(this))
        });
      }),
      takeUntil(this._onDestroy)).subscribe();

    this.autoComplete.closed.pipe(
      tap(() => this.removeScrollEventListener()),
      takeUntil(this._onDestroy)).subscribe();
  }

  private removeScrollEventListener() {
    this.autoComplete.panel.nativeElement
      .removeEventListener('scroll', this.onScroll);
  }

  ngOnDestroy() {
    this._onDestroy.next();
    this._onDestroy.complete();

    this.removeScrollEventListener();
  }

  onScroll(event: Event) {

    if (this.thresholdPercent === undefined) {
      this.scroll.next({ autoComplete: this.autoComplete, scrollEvent: event });
    } else {
      const threshold = this.thresholdPercent * 100 * event.target.scrollHeight / 100;
      const current = event.target.scrollTop + event.target.clientHeight;

      //console.log(`scroll ${current}, threshold: ${threshold}`)
      if (current > threshold) {
        //console.log('load next page');
        this.scroll.next({ autoComplete: this.autoComplete, scrollEvent: event });
      }
    }
  }
}

之后剩下的就是当滚动条达到 80% 阈值时从服务器加载更多数据:

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable, fromEvent, of, Subject, merge, combineLatest } from 'rxjs';
import { map, startWith, switchMap, tap, debounceTime, filter, scan, withLatestFrom, mergeMap, takeUntil, takeWhile, distinctUntilChanged, skipUntil, exhaustMap, endWith } from 'rxjs/operators';
import { MatAutocomplete } from '@angular/material/autocomplete';
import { takeWhileInclusive } from 'rxjs-take-while-inclusive';

export interface ILookup {
  id: number,
  name: string
}
@Component({
  selector: 'autocomplete-filter-example',
  templateUrl: 'autocomplete-filter-example.html',
  styleUrls: ['autocomplete-filter-example.scss'],
})
export class AutocompleteFilterExample implements OnInit {

  searchText = new FormControl({ id: 2, name: 'ana' });
  filteredLookups$: Observable<ILookup[]>;
  private lookups: ILookup[] = [];
  private nextPage$ = new Subject();
  private _onDestroy = new Subject();

  // Fake backend api
  private getProducts(startsWith: string, page: number): Observable<ILookup[]> {
    console.log(`api call filter: ${startsWith}`);

    const take = 10;
    const skip = page > 0 ? (page - 1) * take : 0;

    const filtered = this.lookups
      .filter(option => option.name.toLowerCase().startsWith(startsWith.toLowerCase()))

    console.log(`skip: ${skip}, take: ${take}`);

    return of(filtered.slice(skip, skip + take));
  }

  ngOnInit() {

    // Note: Generate some mock data
    this.lookups = [{ id: 1994, name: 'ana' }, { id: 1989, name: 'narcis' }]
    for (let i = 1; i < 100; i++) {
      this.lookups.push({ id: i, name: 'test' + i })
    }

    // Note: listen for search text changes
    const filter$ = this.searchText.valueChanges.pipe(
      startWith(''),
      debounceTime(200),
      // Note: If the option valye is bound to object, after selecting the option
      // Note: the value will change from string to {}. We want to perform search 
      // Note: only when the type is string (no match)
      filter(q => typeof q === "string"));

    // Note: There are 2 stream here: the search text changes stream and the nextPage$ (raised by directive at 80% scroll)
    // Note: On every search text change, we issue a backend request starting the first page
    // Note: While the backend is processing our request we ignore any other NextPage emitts (exhaustMap).
    // Note: If in this time the search text changes, we don't need those results anymore (switchMap)
    this.filteredLookups$ = filter$.pipe(
      switchMap(filter => {
        //Note: Reset the page with every new seach text
        let currentPage = 1;
        return this.nextPage$.pipe(
          startWith(currentPage),
          //Note: Until the backend responds, ignore NextPage requests.
          exhaustMap(_ => this.getProducts(filter, currentPage)),
          tap(() => currentPage++),
          //Note: This is a custom operator because we also need the last emitted value.
          //Note: Stop if there are no more pages, or no results at all for the current search text.
          takeWhileInclusive(p => p.length > 0),
          scan((allProducts, newProducts) => allProducts.concat(newProducts), []),
        );
      })); // Note: We let asyncPipe subscribe.

  }

  displayWith(lookup) {
    return lookup ? lookup.name : null;
  }

  onScroll() {
    //Note: This is called multiple times after the scroll has reached the 80% threshold position.
    this.nextPage$.next();
  }

  ngOnDestroy() {
    this._onDestroy.next();
    this._onDestroy.complete();
  }
}

注意:我正在使用自定义 rxjs 运算符 rxjs-take-while-inclusive。

您可以在这里看到它的实际效果:DEMO

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Angular Material 6 中用于自动完成的无限滚动 的相关文章

随机推荐

  • 为什么 Collections.Counter 没有对称差异?

    因此 对于集合 您可以执行对称差 这相当于并减交集 为什么 是 Counter 对象不受支持的操作数 而并集和交集仍然有效 扩展我的评论 结果发现它当时被讨论过 但被拒绝了 单击完整消息 及其主题 的链接 我将引用 Raymond Hett
  • 如何移动正交图中的点?

    我正在尝试在迈克 博斯托克创建的以下地图上的某些地理位置添加红点 https bl ocks org mbostock 3795040 我的点会显示 但不会随地图移动 如何编辑代码以使点随地图移动 谢谢 add circles to svg
  • 如何使用javascript从mysql数据库获取数据?

    如果可能的话 如何使用 javascript 从 mysql 数据库获取 并发布 数据 我知道我可以使用 php 和其他语言 但我只需要知道这是否可以用 javascript 实现 提前致谢 这对于 Javascript 来说是不可能的 我
  • 如何响应 UNUserNotification 上的点击?

    我正在使用新的UNUserNotificationiOS 10 中的框架 我可以看到如何添加操作按钮 但是当用户点击通知本身时我如何响应 就我而言 它将是带有一些文本的图像 默认行为是应用程序打开 我可以使用自定义代码来检测我的应用程序是否
  • 如何在pyodbc中使用executemany运行多个SELECT查询

    我使用 PYODBC 根据 pandas 数据帧列的值多次查询 SQL DB 如下所示为值列表 因为我使用 ToList 函数将该列转换为列表 the connection string cnxn pyodbc connect driver
  • 扫描随机数量的浮点数,直到 C 中出现新行

    我正在尝试从包含以下文本的文件中读取 502 601 596 465 464597 599 600 598602 591 596 601588 565 548 260 62 61 595583 595 61 558 561237 241 4
  • 分析 C# 中的方法以了解其运行时间

    我需要获取计时报告以了解在类中运行 C 方法需要多长时间 我考虑使用profiler要做到这一点 输入是类中方法的名称 输出是 什么方法 类调用这个方法 运行该方法的时间量 有哪些工具 商业产品可用于 Visual Studio 2010
  • 在 TypoScript 中获取 FlexForm 配置

    我需要从 pi flexform 获取 typescript 中的 page headerData 如何实现我的要求 page PAGE page headerData 10 TEXT 10 value 我不太确定你真正需要什么 我是gue
  • SlidingDrawer 动画速度

    我是 Android 编程和堆栈溢出的新手 我需要减慢应用程序中 SlidingDrawer 的动画速度 我已经像这样子类化了 SlidingDrawer import android content Context import andr
  • 最大化两个数组元素的乘积之和的算法

    竞赛中有一个问题需要计算仅包含数学和生物科目的班级的表现 所以 没有 数学学生 n 没有 的生物学生 每个学生都有一个单独的分数 数学学生和生物学生的分数分别存储在数组 mathScore 和 bioScore 中 全班成绩计算如下 mat
  • 从存储过程填充 DataGridView

    我使用 SQL Server 2008 创建了一个名为 MyStoreProc 的存储过程 它在管理工具中运行良好 在 VB Net 2008 中 我创建了一个新的数据集和一个新的 TableAdaptor 在此表适配器中 我创建了一个名为
  • 如何从树状数组创建 ul - li 菜单?

    我有一个数组title and children index title始终不为空 children是一个数组 空或非空 Any children have title and children等等 myArray 0 gt title g
  • JTable右键复制/粘贴菜单一键复制单元格数据

    我创建了我的JPopupMenu 它出现在我的JTable当我右键单击一个单元格时 但是 我无法复制单元格中的数据 除非我首先双击然后突出显示数据 然后右键单击当前单元格以外的任何位置以显示弹出菜单和复制选项 我想复制单元格中的数据 而不必
  • Perl - 子例程“Hash::Merge::merge”的深度递归

    下列的this问题 我在那里使用了答案 也发布在这里 现在我失败了 我知道失败可能来自于 return bless self gt merge left right class left 但我不明白可能是什么问题 My code usr b
  • 使用 Windows 服务和 SQL Server 在 OneWay WCF 消息中排队

    我需要为 WCF 服务请求实现一个排队机制 该服务将由客户端以单向方式调用 这些请求消息应存储在 SQL Server 数据库中 并且 Windows 服务对消息进行排队 处理请求的时间是可配置的 如果处理消息时发生错误 则需要重试最多10
  • MySQL 5.7 错误(1093:您无法在 FROM 子句中指定目标表 ___ 进行更新) - 通常的解决方案不起作用

    我有一个表 员工 我试图将一些属性 例如薪水 设置为与表中其他值相同的值 我对这个错误的理解是 可以通过以下解决方法来避免它 使用临时表 UPDATE employees SET salary SELECT salary FROM SELE
  • 当使用非虚拟析构函数“删除”基类时,Clang 和 GCC 会做什么?

    已经有一个问题询问 现实世界 的行为delete指向缺少虚拟析构函数的基类的指针 但问题仅限于非常有限的情况 派生类没有具有非平凡析构函数的成员 并且接受的答案只是说没有办法知道不检查每个编译器的行为 但这实际上并不是很有帮助 知道每个编译
  • authorize.net json返回额外字符

    我有这个代码 ch curl init curl setopt ch CURLOPT URL url curl setopt ch CURLOPT RETURNTRANSFER 1 curl setopt ch CURLOPT HTTPHE
  • Laravel 5 如何在保存时验证每个活动下的唯一客户名称

    我有三个模型 活动模型 客户模型和客户项目模型 如何在商店功能中进行验证检查 使每个活动中的客户名称应该是唯一的 以下是每个迁移文件 活动模型 public function up Schema create activities func
  • Angular Material 6 中用于自动完成的无限滚动

    我正在尝试在 Angular Material 6 中实现自动完成的无限滚动 我的场景很简单 我有一个启用了自动完成功能的输入字段 当用户键入时 我将使用输入字段中的文本进行 HTTP 调用 以将结果显示为建议 但我只想显示 25 条建议