@libs-ui/components-click-outside
v0.2.357-35
Published
> Directive phát hiện click bên ngoài hoặc bên trong một element — hữu ích cho dropdown, modal, popover và mọi UI cần đóng khi người dùng click ra ngoài.
Readme
@libs-ui/components-click-outside
Directive phát hiện click bên ngoài hoặc bên trong một element — hữu ích cho dropdown, modal, popover và mọi UI cần đóng khi người dùng click ra ngoài.
Giới thiệu
LibsUiComponentsClickOutsideDirective là một Angular Standalone Directive lắng nghe sự kiện mousedown trên window và phân biệt click xảy ra bên trong hay bên ngoài element được gắn directive. Directive tự động dọn dẹp subscription khi bị destroy thông qua takeUntilDestroyed, không gây memory leak. Sự kiện được phát sớm hơn so với click do dùng mousedown, giúp UI phản hồi nhanh hơn.
Tính năng
- Phát hiện click bên ngoài element và emit
(outOutside) - Phát hiện click bên trong element và emit
(outInSide) - Sử dụng
mousedownđể bắt sự kiện sớm hơnclick - Tự động cleanup với
takeUntilDestroyed— không cầnngOnDestroy - Standalone directive — import trực tiếp, không cần NgModule
- Không có dependency ngoài Angular và RxJS
Khi nào sử dụng
- Đóng dropdown/menu khi người dùng click bên ngoài
- Đóng modal/dialog khi click vào vùng overlay xung quanh
- Đóng tooltip hoặc popover khi click ra ngoài
- Phát hiện tương tác của người dùng với một vùng element cụ thể
- Implement hành vi "click-away" cho custom components
Cài đặt
npm install @libs-ui/components-click-outsideImport
import { LibsUiComponentsClickOutsideDirective } from '@libs-ui/components-click-outside';
@Component({
standalone: true,
imports: [LibsUiComponentsClickOutsideDirective],
// ...
})
export class MyComponent {}Ví dụ sử dụng
1. Cơ bản — lắng nghe cả hai sự kiện
import { Component } from '@angular/core';
import { LibsUiComponentsClickOutsideDirective } from '@libs-ui/components-click-outside';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsClickOutsideDirective],
template: `
<div
LibsUiComponentsClickOutsideDirective
(outOutside)="onClickOutside($event)"
(outInSide)="onClickInside($event)"
class="p-4 border-2 border-blue-500 rounded-lg">
<p>Click inside or outside this box</p>
</div>
`,
})
export class ExampleComponent {
onClickOutside(event: Event): void {
console.log('Clicked outside!', event);
}
onClickInside(event: Event): void {
console.log('Clicked inside!', event);
}
}2. Dropdown — đóng khi click ra ngoài
import { Component, signal } from '@angular/core';
import { LibsUiComponentsClickOutsideDirective } from '@libs-ui/components-click-outside';
@Component({
standalone: true,
imports: [LibsUiComponentsClickOutsideDirective],
template: `
<div class="relative">
<button
(click)="isOpen.set(!isOpen())"
class="px-4 py-2 bg-blue-500 text-white rounded">
Toggle Dropdown
</button>
@if (isOpen()) {
<div
LibsUiComponentsClickOutsideDirective
(outOutside)="isOpen.set(false)"
class="absolute mt-2 w-48 bg-white border rounded-lg shadow-lg">
<ul class="py-2">
<li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 1</li>
<li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 2</li>
<li class="px-4 py-2 hover:bg-gray-100 cursor-pointer">Option 3</li>
</ul>
</div>
}
</div>
`,
})
export class DropdownExample {
readonly isOpen = signal<boolean>(false);
}3. Modal — đóng khi click vào overlay
import { Component, signal } from '@angular/core';
import { LibsUiComponentsClickOutsideDirective } from '@libs-ui/components-click-outside';
@Component({
standalone: true,
imports: [LibsUiComponentsClickOutsideDirective],
template: `
@if (showModal()) {
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div
LibsUiComponentsClickOutsideDirective
(outOutside)="showModal.set(false)"
class="bg-white p-6 rounded-lg shadow-xl max-w-md">
<h3 class="text-lg font-semibold mb-4">Modal Title</h3>
<p class="text-gray-600 mb-4">Click outside to close this modal</p>
<button
(click)="showModal.set(false)"
class="px-4 py-2 bg-blue-500 text-white rounded">
Close
</button>
</div>
</div>
}
`,
})
export class ModalExample {
readonly showModal = signal<boolean>(false);
}4. Click counter — đếm số lần click inside/outside
import { Component, signal } from '@angular/core';
import { LibsUiComponentsClickOutsideDirective } from '@libs-ui/components-click-outside';
@Component({
standalone: true,
imports: [LibsUiComponentsClickOutsideDirective],
template: `
<div
LibsUiComponentsClickOutsideDirective
(outOutside)="onClickOutside()"
(outInSide)="onClickInside()"
class="p-6 border-2 border-purple-500 rounded-lg bg-purple-50">
<h4 class="font-semibold mb-2">Click Counter</h4>
<p>Inside clicks: {{ insideCount() }}</p>
<p>Outside clicks: {{ outsideCount() }}</p>
</div>
`,
})
export class CounterExample {
readonly insideCount = signal<number>(0);
readonly outsideCount = signal<number>(0);
onClickInside(): void {
this.insideCount.update((count) => count + 1);
}
onClickOutside(): void {
this.outsideCount.update((count) => count + 1);
}
}@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outOutside) | Event | Emit khi mousedown xảy ra bên ngoài element | handlerClickOutside(event: Event): void { event.stopPropagation(); ... } | (outOutside)="handlerClickOutside($event)" |
| (outInSide) | Event | Emit khi mousedown xảy ra bên trong element | handlerClickInside(event: Event): void { event.stopPropagation(); ... } | (outInSide)="handlerClickInside($event)" |
Selector
Directive được gắn vào element bằng attribute selector:
<div LibsUiComponentsClickOutsideDirective ...>Selector:
[LibsUiComponentsClickOutsideDirective]
Lưu ý quan trọng
⚠️ Event Timing: Directive setup listener trong ngAfterViewInit — event chỉ hoạt động sau khi view được khởi tạo hoàn chỉnh.
⚠️ mousedown thay vì click: Directive dùng mousedown để bắt sự kiện sớm hơn click, đảm bảo UI phản hồi ngay khi người dùng nhấn chuột xuống chứ không chờ nhả ra.
⚠️ stopPropagation nội bộ: Bên trong directive, sự kiện đã được gọi stopPropagation() để tránh conflict với các handler ở parent. Tuy nhiên, các handler ở phía consumer (component sử dụng directive) vẫn nên gọi event.stopPropagation() theo convention của project để ngăn sự kiện lan lên trên.
⚠️ Không có @Input(): Directive này không nhận bất kỳ input nào — chỉ cần gắn vào element và lắng nghe output.
⚠️ Cleanup tự động: Subscription được dọn dẹp qua takeUntilDestroyed(destroyRef) — không cần tự unsubscribe hay implement ngOnDestroy.
