我目前具有基本的课程搜索功能,其中包含自定义管道过滤器stackblitz here。它根据course title
,description
或keywords
中是否存在匹配项返回结果,但是它以任意顺序返回课程。我想修改管道以首先返回更相关的结果,优先是搜索查询在1.标题,2。描述,3。关键字中匹配。
一些测试用例示例:
查询"chinese"
当前在"1 French"
之前返回"1X Chinese"
,因为"chinese"
的关键字与"1 French"
匹配。
查询"linear algebra"
在54 Math
之前先返回89A statistics
,即使后者在所有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管道?
任何指针将不胜感激!
答案 0 :(得分: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或filter,它们建议您在组件中自己做这些,而不是使用管道:
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)">
我们不显示所有课程,但显示课程结果...
<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,它使用去抖功能(如果用户输入速度非常快,则不会对每个按键进行整个过滤和排序)和distinctUntilChanged(如果用户将一个字符退格,不会打扰),并创建了Array的全局扩展(sortBy和sortByDesc)。
所以现在我们有了输入:
<input type='text' #searchInput placeholder=" Search a Topic, Subject, or a Course!">
然后将ViewChild作为ElementRef:
@ViewChild('searchInput') searchInputRef: ElementRef;
然后在初始化之后,我们在本机元素上的输入上设置可观察对象并订阅它:
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;
}