一个全局的 SearchService
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class SearchService {
/**
* 输入文字发送改变
*/
static EVENT_CHANGE = 'change';
/**
* 确认搜索
*/
static EVENT_CONFIRM = 'confirm';
/**
* 根据文字设置搜索建议
*/
static EVENT_CHANGE_SUGGEST = 'suggest';
private eventPair: {
[trigger: string]: string;
} = {
[SearchService.EVENT_CHANGE]: SearchService.EVENT_CHANGE_SUGGEST
};
private listeners: {
[key: string]: Function[];
} = {};
constructor(
) {
}
public on(event: 'change', cb: (keywords: string) => void|boolean|Observable<any[]>): this;
public on(event: 'confirm', cb: (keywords: any) => void|false): this;
public on(event: 'suggest', cb: (items: any[]) => void): this;
public on(event: string, cb: (...items: any[]) => void|boolean|Observable<any>): this;
public on(event: string, cb: any) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, event)) {
this.listeners[event] = [];
}
this.listeners[event].push(cb);
return this;
}
public emit(event: 'change', keywords: string): this;
public emit(event: 'confirm', keywords: any): this;
public emit(event: 'suggest', items: any[]): this;
public emit(event: string, ...items: any[]): this;
public emit(event: string, ...items: any[]) {
if (!Object.prototype.hasOwnProperty.call(this.listeners, event)) {
return this;
}
const pair = this.eventPair[event];
const listeners = this.listeners[event];
for (let i = listeners.length - 1; i >= 0; i--) {
const cb = listeners[i];
const res = cb(...items);
// 允许事件不进行传递
if (res === false) {
break;
}
if (!res || !pair) {
continue;
}
// 接受订阅
if (res instanceof Observable) {
res.subscribe(data => {
this.emit(pair, data);
});
continue;
}
this.emit(pair, res);
}
return this;
}
public off(...events: string[]): this;
public off(event: string, cb: Function): this;
public off(...events: any[]) {
if (events.length == 2 && typeof events[1] === 'function') {
return this.offListener(events[0], events[1]);
}
for (const event of events) {
delete this.listeners[event];
}
return this;
}
/**
* 移除搜索框页面的接受事件
*/
public offTrigger() {
return this.off(SearchService.EVENT_CHANGE_SUGGEST);
}
/**
* 移除搜索结果页面的接受事件
*/
public offReceiver() {
return this.off(SearchService.EVENT_CHANGE, SearchService.EVENT_CONFIRM);
}
private offListener(event: string, cb: Function): this {
if (!Object.prototype.hasOwnProperty.call(this.listeners, event)) {
return this;
}
const items = this.listeners[event];
for (let i = items.length - 1; i >= 0; i--) {
if (items[i] === cb) {
items.splice(i, 1);
}
}
return this;
}
}
注册全局 service
export class ThemeModule {
static forRoot(): ModuleWithProviders<ThemeModule> {
return {
ngModule: ThemeModule,
providers: [
SearchService,
]
};
}
}
添加搜索框
<div class="dialog-search" [ngClass]="{inputting: suggestText.length > 0}" [hidden]="!panelVisible">
<i class="iconfont icon-close dialog-close" (click)="close()"></i>
<div class="dialog-body">
<div class="search-input">
<i class="iconfont icon-search input-search"></i>
<input type="text" placeholder="请输入关键字,按回车 / Enter 搜索" autocomplete="off" [(ngModel)]="suggestText" (keydown)="suggestKeyPress($event)" (ngModelChange)="onSuggestChange()">
<i class="iconfont icon-close input-clear" (click)="tapClear()"></i>
</div>
<ul class="search-suggestion">
<li *ngFor="let item of suggestItems;let i = index" [ngClass]="{active: i === suggestIndex}" (click)="tapItem(item)"><span>{{ i + 1 }}</span>{{ formatTitle(item) }}</li>
</ul>
</div>
</div>
import { Component, OnDestroy, OnInit } from '@angular/core';
import { SearchService } from '../../theme/services';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit, OnDestroy {
public panelVisible = false;
public suggestItems: any[] = [];
public suggestText = '';
public suggestIndex = -1;
private asyncHandle = 0;
constructor(
private searchService: SearchService,
) { }
ngOnInit() {
this.searchService.on('suggest', items => {
this.suggestIndex = -1;
this.suggestItems = items;
});
}
ngOnDestroy() {
this.searchService.offTrigger();
}
public formatTitle(item: any) {
if (typeof item !== 'object') {
return item;
}
// 这里只接受 title 或 name 属性进行显示
return item.title || item.name;
}
public suggestKeyPress(event: KeyboardEvent) {
if (event.key === 'Enter') {
this.searchService.emit('confirm', this.suggestIndex >= 0 ? this.suggestItems[this.suggestIndex] : this.suggestText);
this.close();
return;
}
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
this.suggestIndex = -1;
return;
}
if (this.suggestItems.length < 0) {
return;
}
let i = this.suggestIndex;
if (event.key === 'ArrowDown') {
i = i < this.suggestItems.length - 1 ? i + 1 : 0;
} else if (event.key === 'ArrowUp') {
i = (i < 1 ? this.suggestItems.length: i) - 1;
}
this.suggestIndex = i;
this.suggestText = this.formatTitle(this.suggestItems[this.suggestIndex]);
}
public onSuggestChange() {
if (this.suggestIndex >= 0) {
return;
}
this.asyncSuggest();
}
public tapItem(item: any) {
this.searchService.emit('confirm', item);
this.close();
}
public tapClear() {
this.suggestText = '';
this.suggestItems = [];
}
public open() {
this.panelVisible = true;
}
public close() {
this.panelVisible = false;
}
private asyncSuggest() {
if (this.asyncHandle) {
clearTimeout(this.asyncHandle);
}
this.suggestIndex = -1;
this.asyncHandle = window.setTimeout(() => {
this.asyncHandle = 0;
this.suggestIndex = -1;
if (this.suggestText.length < 1) {
this.suggestItems = [];
return;
}
this.searchService.emit('change', this.suggestText);
}, 300);
}
}
搜索页面
其他页面,根据搜索关键词跳转搜索页面或详情页。
export class BlogComponent implements OnInit, OnDestroy {
constructor(
private searchService: SearchService,
private service: BlogService,
private router: Router,
private route: ActivatedRoute,
) {
}
ngOnInit() {
this.searchService.on('change', keywords => {
return this.service.suggesttion({keywords});
}).on('confirm', res => {
if (typeof res === 'object') {
this.router.navigate([res.id], {relativeTo: this.route});
return;
}
this.router.navigate(['./'], {relativeTo: this.route, queryParams: {
keywords: res
}});
});
}
ngOnDestroy() {
this.searchService.offReceiver();
}
}
如果当前就是搜索页面,那么可以不用跳转
private searchFn = res => {
if (typeof res === 'object') {
return;
}
this.queries.keywords = res;
this.tapRefresh();
// 阻止事件传递
return false;
};
ngOnInit() {
this.searchService.on('confirm', this.searchFn);
}
ngOnDestroy() {
this.searchService.off('confirm', this.searchFn);
}
注意
搜索事件并不会自动清除,需要添加 ngOnDestroy
进行清除
转载请保留原文链接: https://zodream.cn/blog/id/210.html