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

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

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

angular 21 升级使用 signals 方案笔记-zodream梦想开源/个人编程日记 文件解析笔记-zodream梦想开源/个人编程日记 密码本开发笔记之读写与保存-zodream梦想开源/个人编程日记 基于 SkiaSharp 的轮廓获取-zodream梦想开源/个人编程日记 SkiaSharp 把 pixel byte[] 转成 SKBitmap-zodream梦想开源/个人编程日记 nas 使用 Docker 安装 gogs-zodream梦想开源/个人编程日记 复制 android 手机中的文件到电脑-zodream梦想开源/个人编程日记 最新|个人日记-zodream梦想开源/个人编程日记 升级 SiteServer CMS 并迁移到 Linux 服务器-zodream梦想开源/个人编程日记 最新|个人日记-zodream梦想开源/个人编程日记 最新|个人日记-zodream梦想开源/个人编程日记 最新|个人日记-zodream梦想开源/个人编程日记 周报:寻找优质的周刊-zodream梦想开源/个人编程日记 开发日志:对Markdown的代码块新增引用来源支持-zodream梦想开源/个人编程日记 周报:怎么写技术类的教程文章-zodream梦想开源/个人编程日记 css display:flex 布局尺寸超出问题-zodream梦想开源/个人编程日记 周报:SEO优化的思考-zodream梦想开源/个人编程日记 Edge 浏览器不适用 Edge Image Viewer 打开图片 -zodream梦想开源/个人编程日记 SEO 学习笔记(一) 内容来源-zodream梦想开源/个人编程日记 PHP 实现双因素身份认证(2FA)-zodream梦想开源/个人编程日记 winui3 自定义标题栏-zodream梦想开源/个人编程日记 WPF MVVM 获取List 多选数据-zodream梦想开源/个人编程日记 php 接入 WebAuthn 登录-zodream梦想开源/个人编程日记 Burp Suite 抓包-zodream梦想开源/个人编程日记 lnmp php集成环境安装包使用-zodream梦想开源/个人编程日记 js 进行在线编辑器开发-zodream梦想开源/个人编程日记 使用 indexnow 注意事项-zodream梦想开源/个人编程日记 Godot 使用字体图标 例如: Iconfont、FontAwesome-zodream梦想开源/个人编程日记 angular 15 对指定页面进行访问限制-zodream梦想开源/个人编程日记 CSS 使用 column-count 实现瀑布流出现内容分割的解决办法-zodream梦想开源/个人编程日记 angular 15 实现按下确认键,焦点移动到下一个表单或提交表单-zodream梦想开源/个人编程日记 input 确认按键事件在手机端不生效-zodream梦想开源/个人编程日记 C# 使用socket 进行通讯-zodream梦想开源/个人编程日记 Maui开发中Windows应用开启管理员权限-zodream梦想开源/个人编程日记 Maui 中自定义控件-zodream梦想开源/个人编程日记 TencentOS Server 3.1 安装 Nginx 1.23、PHP 8.2、MariaDB 10.11-zodream梦想开源/个人编程日记 angular 14 使用 ng-template 实现tree 结构显示-zodream梦想开源/个人编程日记 angular 14 替换 ComponentFactoryResolver 实现动态创建组件-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++ 的dll-zodream梦想开源/个人编程日记 c# 重写 c++ 程序笔记:遍历-zodream梦想开源/个人编程日记 Net Core 与 UWP 共用类开发-zodream梦想开源/个人编程日记 hashcat(二)找回rar解压密码-zodream梦想开源/个人编程日记 Godot 学习笔记(一)-zodream梦想开源/个人编程日记 升级vue3记录-zodream梦想开源/个人编程日记 angular 12 显示数学公式-zodream梦想开源/个人编程日记 js 监听按键事件-zodream梦想开源/个人编程日记 angular 12 ng-deep 使用注意事项-zodream梦想开源/个人编程日记 angular 16 动态生成组件-zodream梦想开源/个人编程日记 angular 12 动画执行完成事件-zodream梦想开源/个人编程日记 angular 12 中单例 Service 的使用-zodream梦想开源/个人编程日记 js 实现一个正则替换-zodream梦想开源/个人编程日记 uwp win2d 使用-zodream梦想开源/个人编程日记 UWP Custom Control自定义控件开发-zodream梦想开源/个人编程日记 UWP 读取应用内资源-zodream梦想开源/个人编程日记 gin 使用笔记(二)出错点-zodream梦想开源/个人编程日记 gin 使用笔记(一)基础-zodream梦想开源/个人编程日记 angular 关于自定义组件事件传递-zodream梦想开源/个人编程日记 angular 11 怎么获取 Content-Disposition-zodream梦想开源/个人编程日记 apache 使用gzip 压缩 js、css-zodream梦想开源/个人编程日记 angular 11 返回上一页保留页面数据的思考-zodream梦想开源/个人编程日记 一个简单的HTML音视频播放器-zodream梦想开源/个人编程日记 Net Core 实现一个简单的分页功能-zodream梦想开源/个人编程日记 关于内容中的 @用户 加 话题 的一些想法-zodream梦想开源/个人编程日记 Github Host 更改-zodream梦想开源/个人编程日记 OBS-Studio 等录屏软件录制显示器内容的黑屏的解决方法-zodream梦想开源/个人编程日记 angular 11 FormBuilder 中 FormGroup 和 FormArray 使用-zodream梦想开源/个人编程日记 angular 11 ngrx/effects 使用理解-zodream梦想开源/个人编程日记 angular 11 ngrx/store 使用理解-zodream梦想开源/个人编程日记 angular 10 直接获取表单值-zodream梦想开源/个人编程日记 angular 10 使用 tinymce 编辑器-zodream梦想开源/个人编程日记 htaccess 搭配 angular 10 放在二级目录-zodream梦想开源/个人编程日记 微信小程序跨页面传值-zodream梦想开源/个人编程日记 js 对 FileList 进行文件过滤上传-zodream梦想开源/个人编程日记 angular自定义表单组件支持 formControlName-zodream梦想开源/个人编程日记 基于不同形式的json响应处理-zodream梦想开源/个人编程日记 flutter CupertinoPicker 使用不显示-zodream梦想开源/个人编程日记 CC协议-zodream梦想开源/个人编程日记 flutter margin 负值实现-zodream梦想开源/个人编程日记 win10添加删除开机自启项-zodream梦想开源/个人编程日记 Wallpager Engine 删除记录-zodream梦想开源/个人编程日记 angular10教程之http 拦截器-zodream梦想开源/个人编程日记 dpl 文件-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梦想开源/个人编程日记
angular 12 全局搜索组件-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