惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
博客园 - 【当耐特】
WordPress大学
WordPress大学
爱范儿
爱范儿
美团技术团队
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
小众软件
小众软件
量子位
Hugging Face - Blog
Hugging Face - Blog
B
Blog RSS Feed
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
H
Hackread – Cybersecurity News, Data Breaches, AI and More
腾讯CDC
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
博客园 - 叶小钗
GbyAI
GbyAI
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Full Disclosure
G
Google Developers Blog
D
Docker
T
Tailwind CSS Blog
C
Check Point Blog
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
T
The Blog of Author Tim Ferriss
B
Blog
博客园 - 三生石上(FineUI控件)
博客园 - Franky
H
Help Net Security
MyScale Blog
MyScale Blog
U
Unit 42
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog
L
LangChain Blog
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog

zodream梦想开源/个人编程日记

文件解析笔记-zodream梦想开源/个人编程日记 密码本开发笔记之读写与保存-zodream梦想开源/个人编程日记 SkiaSharp 把 pixel byte[] 转成 SKBitmap-zodream梦想开源/个人编程日记 nas 使用 Docker 安装 gogs-zodream梦想开源/个人编程日记 复制 android 手机中的文件到电脑-zodream梦想开源/个人编程日记 周报:寻找优质的周刊-zodream梦想开源/个人编程日记 开发日志:对Markdown的代码块新增引用来源支持-zodream梦想开源/个人编程日记 周报:怎么写技术类的教程文章-zodream梦想开源/个人编程日记 css display:flex 布局尺寸超出问题-zodream梦想开源/个人编程日记 周报:SEO优化的思考-zodream梦想开源/个人编程日记 Edge 浏览器不适用 Edge Image Viewer 打开图片 -zodream梦想开源/个人编程日记 SEO 学习笔记(一) 内容来源-zodream梦想开源/个人编程日记 PHP 实现双因素身份认证(2FA)-zodream梦想开源/个人编程日记 WPF MVVM 获取List 多选数据-zodream梦想开源/个人编程日记 Burp Suite 抓包-zodream梦想开源/个人编程日记 使用 indexnow 注意事项-zodream梦想开源/个人编程日记 Godot 使用字体图标 例如: Iconfont、FontAwesome-zodream梦想开源/个人编程日记 angular 15 对指定页面进行访问限制-zodream梦想开源/个人编程日记 CSS 使用 column-count 实现瀑布流出现内容分割的解决办法-zodream梦想开源/个人编程日记 input 确认按键事件在手机端不生效-zodream梦想开源/个人编程日记 C# 使用socket 进行通讯-zodream梦想开源/个人编程日记 Maui开发中Windows应用开启管理员权限-zodream梦想开源/个人编程日记 Maui 中自定义控件-zodream梦想开源/个人编程日记 angular 14 使用 ng-template 实现tree 结构显示-zodream梦想开源/个人编程日记 c# 动态安装和卸载dll-zodream梦想开源/个人编程日记 慎用 CompositionTarget.Rendering-zodream梦想开源/个人编程日记 c# 重写 c++ 程序笔记:数据初始化-zodream梦想开源/个人编程日记 源码编译 aseprite-zodream梦想开源/个人编程日记 记录一下字符串分隔split各语言之间的不同-zodream梦想开源/个人编程日记 c# Gzip解码无头内容-zodream梦想开源/个人编程日记 Windows 10 查看内存占用-zodream梦想开源/个人编程日记 UWP 使用 win2d:加阴影-zodream梦想开源/个人编程日记 清除 PowerShell 历史记录-zodream梦想开源/个人编程日记 c# 重写 c++ 程序笔记:遍历-zodream梦想开源/个人编程日记 Net Core 与 UWP 共用类开发-zodream梦想开源/个人编程日记 hashcat(二)找回rar解压密码-zodream梦想开源/个人编程日记 Godot 学习笔记(一)-zodream梦想开源/个人编程日记 angular 12 显示数学公式-zodream梦想开源/个人编程日记 js 监听按键事件-zodream梦想开源/个人编程日记 angular 12 ng-deep 使用注意事项-zodream梦想开源/个人编程日记 angular 12 动画执行完成事件-zodream梦想开源/个人编程日记 angular 12 中单例 Service 的使用-zodream梦想开源/个人编程日记 js 实现一个正则替换-zodream梦想开源/个人编程日记 uwp win2d 使用-zodream梦想开源/个人编程日记 UWP 读取应用内资源-zodream梦想开源/个人编程日记 gin 使用笔记(二)出错点-zodream梦想开源/个人编程日记 gin 使用笔记(一)基础-zodream梦想开源/个人编程日记 angular 关于自定义组件事件传递-zodream梦想开源/个人编程日记 apache 使用gzip 压缩 js、css-zodream梦想开源/个人编程日记 关于内容中的 @用户 加 话题 的一些想法-zodream梦想开源/个人编程日记 Github Host 更改-zodream梦想开源/个人编程日记 OBS-Studio 等录屏软件录制显示器内容的黑屏的解决方法-zodream梦想开源/个人编程日记 angular 11 ngrx/effects 使用理解-zodream梦想开源/个人编程日记 angular 11 ngrx/store 使用理解-zodream梦想开源/个人编程日记 angular 10 直接获取表单值-zodream梦想开源/个人编程日记 htaccess 搭配 angular 10 放在二级目录-zodream梦想开源/个人编程日记 微信小程序跨页面传值-zodream梦想开源/个人编程日记 js 对 FileList 进行文件过滤上传-zodream梦想开源/个人编程日记 angular自定义表单组件支持 formControlName-zodream梦想开源/个人编程日记 flutter CupertinoPicker 使用不显示-zodream梦想开源/个人编程日记 CC协议-zodream梦想开源/个人编程日记 flutter margin 负值实现-zodream梦想开源/个人编程日记 win10添加删除开机自启项-zodream梦想开源/个人编程日记 微信小程序开发记录(一)真机无法进入页面-zodream梦想开源/个人编程日记 flutter 跳转页面操作上一页-zodream梦想开源/个人编程日记 Regex Generator 使用指南-zodream梦想开源/个人编程日记 go init函数-zodream梦想开源/个人编程日记 angular 9 升级 angular 10-zodream梦想开源/个人编程日记 kotlin AndroidManifest 注意事项-zodream梦想开源/个人编程日记 对于zodream 框架的优化的思考-zodream梦想开源/个人编程日记 flutter 页面滚动条-zodream梦想开源/个人编程日记 flutter swiper 使用-zodream梦想开源/个人编程日记 flutter API请求-zodream梦想开源/个人编程日记 网站体检之xss攻击-zodream梦想开源/个人编程日记 GitHub 下载慢怎么办-zodream梦想开源/个人编程日记 小程序商城开发总结-zodream梦想开源/个人编程日记 gulp-vue2mini 更新日志-zodream梦想开源/个人编程日记 UWP TextBlock 换行-zodream梦想开源/个人编程日记 js 数组排序 sort-zodream梦想开源/个人编程日记 抛弃create.js 改用pixi.js-zodream梦想开源/个人编程日记 后台列表页进阶之路-zodream梦想开源/个人编程日记 局域网文件传输工具-zodream梦想开源/个人编程日记 angular9教程之PullToRefresh-zodream梦想开源/个人编程日记 c# 添加防火墙例外端口-zodream梦想开源/个人编程日记 c# 获取局域网ip-zodream梦想开源/个人编程日记 angular9教程之自定义部件及获取页面元素-zodream梦想开源/个人编程日记 netcore 依赖注入 AddTransient、AddScoped、AddSingleton-zodream梦想开源/个人编程日记 createjs 实现封装 drawImage-zodream梦想开源/个人编程日记 笔记(200521)游戏及玩家分类-zodream梦想开源/个人编程日记 关于 MasterDetail 模式页面收集-zodream梦想开源/个人编程日记 怎么写日记(一)格式规范-zodream梦想开源/个人编程日记 angular9教程之路由-zodream梦想开源/个人编程日记 PHP 常用框架-zodream梦想开源/个人编程日记 angular9教程之监听子路由变化-zodream梦想开源/个人编程日记 angular9教程之添加启动加载动画-zodream梦想开源/个人编程日记 angular9教程之分模块开发-zodream梦想开源/个人编程日记 angular9教程之使用-zodream梦想开源/个人编程日记 create.js 开发小游戏(二)蒙版-zodream梦想开源/个人编程日记 createjs 游戏结束后重新开始 Tween 补间动画无效-zodream梦想开源/个人编程日记 SEO(二)关键词优化试验-zodream梦想开源/个人编程日记
angular 12 全局搜索组件-zodream梦想开源/个人编程日记
zodream · 2021-06-29 · via zodream梦想开源/个人编程日记

一个全局的 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;
    }

}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120

注册全局 service

export class ThemeModule {
    static forRoot(): ModuleWithProviders<ThemeModule> {
        return {
            ngModule: ThemeModule,
            providers: [
                SearchService,
            ]
        };
    }
}

12345678910

@NgModule({
    imports: [
        ThemeModule.forRoot(),
    ]
})
export class AppModule { }

123456

添加搜索框

<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>

12345678910111213

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);
    }
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104

搜索页面

其他页面,根据搜索关键词跳转搜索页面或详情页。

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();
    }
}

12345678910111213141516171819202122232425262728

如果当前就是搜索页面,那么可以不用跳转

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);
}

12345678910111213141516

注意

搜索事件并不会自动清除,需要添加 ngOnDestroy 进行清除

转载请保留原文链接: https://zodream.cn/blog/id/210.html