【问题标题】:Implement custom search relevancy using an Angular pipe使用 Angular 管道实现自定义搜索相关性
【发布时间】:2018-07-23 03:36:20
【问题描述】:

我目前有一个带有自定义管道过滤器stackblitz here 的基本课程搜索功能。它根据course titledescriptionkeywords 中是否存在匹配返回结果,但它以任意顺序返回课程。我想修改管道以首先返回更相关的结果,优先级是搜索查询匹配 1. 标题,2. 描述,3. 关键字。

一些测试用例示例:

  1. 查询"chinese" 当前在"1X Chinese" 之前返回"1 French",因为"chinese" 的关键字与"1 French" 匹配。

  2. 查询"linear algebra"89A statistics 之前首先返回54 Math,即使后者在所有3 个字段中都匹配。

实现这一点的最佳方法是什么?我目前正在查看this response,这意味着我可以尝试使用以下逻辑(在伪代码中)定义我自己的比较器

orderByComparator(a:Course, b:Course):number{
    if(searchRelevancy(a) < searchRelevancy(b)) return -1;
    else if(searchRelevancy(a) > searchRelevancy(b)) return 1;
    else return 0;
  }

searchRelevancy 可以是一个函数,根据查询计算每门课程的排名

searchRelevancy(course: Course, query: string): number{
   var rank: number;
   if(course.course_title.toUpperCase().includes(query)) rank += 5;
   if(course.description.toUpperCase().includes(query)) rank += 3;
   if(course.keywords.toUpperCase().includes(query)) rank += 1;
return rank
}

视图将包括

  <li *ngFor="let course of courses | courseFilter: searchText |
 orderBy: ['title', 'description', ''keywords]">

但是,我对 Angular 比较陌生,实现细节超出了我目前所能理解的范围。这是最直接的方法吗?如果我知道我只是在过滤课程对象,是否可以在不定义第二个 orderBy 管道的情况下修改当前的 course-filter 管道?

任何指针将不胜感激!

【问题讨论】:

    标签: angular pipe angular-pipe


    【解决方案1】:

    您首先要为每门课程添加排名,然后按此排序。即

    addRank(a: Course, query: string): number{
    
        var rank: number;
        if(course.course_title.toUpperCase().includes(query)) rank += 5;
        if(course.description.toUpperCase().includes(query)) rank += 3;
        if(course.keywords.toUpperCase().includes(query)) rank += 1;
        course.rank = rank
    

    然后按该排名降序排列:

    <li *ngFor="let course of courses | addRank: searchText | orderBy: '-rank'">
    

    但是,Angular(与 AngularJs/Angular 1 不同)不提供 orderBy 或过滤器,他们建议您在组件中自己执行这些操作,而不是使用管道:

    Angular 不提供用于过滤或排序列表的管道。 熟悉 AngularJS 的开发人员将它们称为 filter 和 orderBy。 Angular 中没有等价物。

    这不是疏忽。 Angular 不提供这样的管道,因为它们 表现不佳并防止激进的缩小。

    参考:AngularPipes

    考虑到这一点,您可能会使用一个单独的 courseResults 列表,它是 course 数组的过滤和有序版本。当然,您可以在输入任何搜索/过滤条件之前将其初始化为课程。您还可以通过在搜索输入中添加debounce 来尽量减少不必要的搜索/过滤调用(如果您在键入时这样做;如果您在单击按钮时这样做,那么您会跳过它) .

    简单的解决方案:

    因此,在您的 StackBlitz 的基础上,我完全摆脱了使用管道,创建了一个新的 StackBlitz

    <input type='text' [(ngModel)]='searchText' placeholder=" Search a Topic, Subject, or a Course!" (keyup)="search($event)"> 
    

    我们不显示所有课程,而是显示 courseResults...:

    <ul>
        <li *ngFor="let course of courseResults">
    

    ...初始化为空或所有课程(如这里):

    public courseResults: Course[] = this.courses;
    

    然后搜索可以是:

    search($event) {
      this.courseResults = this.sortByRank(this.rankAndFilter(this.courses, this.searchText.toUpperCase()));
    }
    
    private rankAndFilter(courses: Course[], query: string) {
      var result = [];
      var rank: number;
    
      for (let course of courses) {
        rank = 0;
        if(course.course_title.toUpperCase().includes(query)) {rank += 5};
        if(course.description.toUpperCase().includes(query)) {rank += 3};
        if(course.keywords.toUpperCase().includes(query)) {rank += 1};
        course.rank = rank;
        if (rank > 0) result.push(course);
      }      
      return result;
    }
    
    private sortByRank(courses: Course[]) {
      return courses.sort(function(a:Course,b:Course){
        return b.rank - a.rank;
      });
    }
    

    扩展解决方案:

    [Edit] 一个新的forked StackBlitz,它使用 debounce(如果用户键入速度非常快,则不会对每个按键进行整个过滤和排序)和 distinctUntilChanged(如果用户退格一个字符,它不会t 麻烦),并创建了 Array 的全局扩展(sortBy 和 sortByDesc)。

    所以现在我们有了输入:

    <input type='text' #searchInput placeholder=" Search a Topic, Subject, or a Course!"> 
    

    然后将 ViewChild 作为 ElementRef:

    @ViewChild('searchInput') searchInputRef: ElementRef;
    

    然后在初始化之后,我们在原生元素的输入上设置 observable 并订阅它:

    ngAfterViewInit() {
      var subscription = fromEvent(this.searchInputRef.nativeElement, 'input').pipe(
        map((e: KeyboardEvent) => (<HTMLInputElement>e.target).value),
        debounceTime(400),
        distinctUntilChanged()
        //switchMap(() => this.performSearch())
      );
      subscription.subscribe(data => {
        this.performSearch(data);
      });
    
    }  
    
    private performSearch(searchText) {
      console.log("Search: " + searchText);
      this.searchText = searchText;
      this.courseResults = this.rankAndFilter(this.courses, searchText).sortByDesc('rank'); 
      return this.courseResults;
    }
    

    【讨论】:

    • 感谢您的帮助,我根据您的建议更新了stackblitz,但无法获得所需的输出。我做错了什么?谢谢!
    • 啊,直到你在这里再次指出,我才注意到你的堆栈闪电战。更新了适当的更改。当然,需要精巧,但出于性能和最小化原因(即不再使用管道),这是需要完成的基本方式。
    • 你能分享你的分叉链接吗?我更多地研究了角度应用程序中的过滤和排序数据,并在将过滤逻辑移动到组件本身(如您所建议的那样)中进行了修改,但在实现排序逻辑时遇到了困难。谢谢!
    • @Matt 我为简单版本和使用 debounce 和 distinctUntilChanged(也是 Array 的全局扩展)的版本提供了 StackBlitz 分支。
    • 非常感谢您的帮助!我计划解析 course_numbers 中的数字并实现另一个过滤器,以允许用户根据数字 >= 100 和/或
    猜你喜欢
    • 2017-10-11
    • 1970-01-01
    • 2018-03-28
    • 1970-01-01
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多