@libs-ui/components-device-support
v0.2.357-16
Published
> Directive xử lý sự kiện click đa thiết bị, tự động chọn event type phù hợp (click / pointerdown / touchstart) dựa trên loại thiết bị đang dùng.
Readme
@libs-ui/components-device-support
Directive xử lý sự kiện click đa thiết bị, tự động chọn event type phù hợp (click / pointerdown / touchstart) dựa trên loại thiết bị đang dùng.
Giới thiệu
LibsUiComponentsDeviceSupportHandleClickDirective giải quyết bài toán tương thích click event trên nhiều loại thiết bị: desktop, mobile, và touch screen. Thay vì phải tự phát hiện và bind đúng event type, directive tự động sử dụng getEventNameHandleClick từ @libs-ui/utils để chọn event phù hợp. Mặc định, directive gọi stopPropagation() trên mỗi event để ngăn bubble lên parent, hành vi này có thể tắt qua input [ignoreStopPropagation].
Tính năng
- ✅ Tự động phát hiện loại thiết bị và chọn event type phù hợp (
click/pointerdown/touchstart) - ✅ Tự động gọi
stopPropagation()theo mặc định, có thể tắt khi cần - ✅ Hỗ trợ gắn click listener vào element tùy chỉnh (không nhất thiết là host element)
- ✅ Tự động cleanup subscription qua
DestroyRef+takeUntilDestroyed, không rò rỉ bộ nhớ - ✅ Standalone directive, dễ import vào bất kỳ component nào
Khi nào sử dụng
- Khi cần xử lý click event hoạt động đúng trên cả desktop và mobile/touch devices
- Khi cần tự động phát hiện loại thiết bị và chọn event phù hợp thay vì hardcode
(click) - Khi muốn
stopPropagationđược xử lý tự động, không cần gọi thủ công trong handler - Khi cần gắn click listener vào một element bên ngoài host (ví dụ:
window,document, hoặc mộtHTMLElementtùy chọn) - Khi xây dựng component dùng chung cần tương thích nhiều thiết bị
Cài đặt
npm install @libs-ui/components-device-supportImport
import { LibsUiComponentsDeviceSupportHandleClickDirective } from '@libs-ui/components-device-support';
@Component({
standalone: true,
imports: [LibsUiComponentsDeviceSupportHandleClickDirective],
// ...
})
export class MyComponent {}Ví dụ sử dụng
1. Sử dụng cơ bản trên button
import { Component } from '@angular/core';
import { LibsUiComponentsDeviceSupportHandleClickDirective } from '@libs-ui/components-device-support';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsDeviceSupportHandleClickDirective],
template: `
<button
libsUiComponentsDeviceSupportHandleClickDirective
(outClick)="onButtonClick($event)"
class="px-4 py-2 bg-blue-500 text-white rounded">
Click me
</button>
`
})
export class ExampleComponent {
onButtonClick(event: Event): void {
console.log('Clicked!', event.type);
}
}2. Gắn click listener vào element tùy chỉnh (không phải host)
Hữu ích khi directive host ẩn nhưng cần lắng nghe click trên một element khác trong DOM.
import { Component } from '@angular/core';
import { LibsUiComponentsDeviceSupportHandleClickDirective } from '@libs-ui/components-device-support';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsDeviceSupportHandleClickDirective],
template: `
<!-- Element sẽ được lắng nghe click -->
<div #targetElement class="p-4 border rounded bg-gray-50">
Target Element - Click vào đây
</div>
<!-- Directive host — lắng nghe trên targetElement, không phải div này -->
<div
libsUiComponentsDeviceSupportHandleClickDirective
[elementHandleClick]="targetElement"
(outClick)="onTargetClick($event)">
Directive host (không click vào đây)
</div>
`
})
export class ExampleComponent {
onTargetClick(event: Event): void {
console.log('Target clicked!', event.type);
}
}3. Kiểm soát stopPropagation — cho phép event bubble lên parent
import { Component } from '@angular/core';
import { LibsUiComponentsDeviceSupportHandleClickDirective } from '@libs-ui/components-device-support';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsDeviceSupportHandleClickDirective],
template: `
<!-- Với stopPropagation (mặc định) -->
<div (click)="onParentClick()">
<button
libsUiComponentsDeviceSupportHandleClickDirective
(outClick)="onChildClick($event)">
stopPropagation: ON (parent sẽ KHÔNG nhận click)
</button>
</div>
<!-- Không stopPropagation -->
<div (click)="onParentClick()">
<button
libsUiComponentsDeviceSupportHandleClickDirective
[ignoreStopPropagation]="true"
(outClick)="onChildClick($event)">
stopPropagation: OFF (parent SẼ nhận click)
</button>
</div>
`
})
export class ExampleComponent {
onChildClick(event: Event): void {
console.log('Child clicked');
}
onParentClick(): void {
console.log('Parent clicked');
}
}4. Kết hợp với Signal để theo dõi trạng thái tương tác
import { Component, signal } from '@angular/core';
import { LibsUiComponentsDeviceSupportHandleClickDirective } from '@libs-ui/components-device-support';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsDeviceSupportHandleClickDirective],
template: `
<div>
<button (click)="toggleStopPropagation()">
Toggle stopPropagation
</button>
<div (click)="onParentClick()">
<button
libsUiComponentsDeviceSupportHandleClickDirective
[ignoreStopPropagation]="ignoreStopPropagation()"
(outClick)="onInteractiveClick($event)"
class="px-4 py-2 bg-blue-500 text-white rounded">
Click me
</button>
</div>
<p>Click count: {{ clickCount() }}</p>
<p>Last event type: {{ lastEventType() }}</p>
<p>ignoreStopPropagation: {{ ignoreStopPropagation() }}</p>
</div>
`
})
export class ExampleComponent {
readonly ignoreStopPropagation = signal<boolean>(false);
readonly clickCount = signal<number>(0);
readonly lastEventType = signal<string>('');
toggleStopPropagation(): void {
this.ignoreStopPropagation.update((v) => !v);
}
onInteractiveClick(event: Event): void {
this.clickCount.update((count) => count + 1);
this.lastEventType.set(event.type);
}
onParentClick(): void {
console.log('Parent clicked');
}
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [elementHandleClick] | HTMLElement \| Window \| Document \| undefined | host element | Element để lắng nghe event click. Khi không truyền, directive sẽ lắng nghe trên chính host element | [elementHandleClick]="targetEl" |
| [ignoreStopPropagation] | boolean | false | Khi true, directive sẽ không gọi stopPropagation() — event sẽ bubble lên parent elements như bình thường | [ignoreStopPropagation]="true" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outClick) | Event | Emit event khi user click/touch vào element. Event type phụ thuộc vào loại thiết bị (click, pointerdown, hoặc touchstart) | handlerClick(event: Event): void { event.stopPropagation(); console.log(event.type); } | (outClick)="handlerClick($event)" |
Logic ẩn quan trọng
Auto Event Detection
Directive sử dụng hàm getEventNameHandleClick từ @libs-ui/utils để tự động chọn event type phù hợp với thiết bị:
- Touch device (có
onpointerdownvàmaxTouchPoints > 0): sử dụngpointerdown - Desktop: sử dụng
click - Mobile (non-pointer touch): sử dụng
touchstart
Việc này đảm bảo click handler luôn phản hồi nhanh và chính xác trên mọi nền tảng.
Default stopPropagation
Mặc định ignoreStopPropagation = false, tức là directive luôn gọi e.stopPropagation() trước khi emit outClick. Điều này ngăn event bubble lên các parent elements, tránh trigger click handler cha không mong muốn.
Để cho phép event bubble (ví dụ: khi cần parent cũng nhận click), truyền [ignoreStopPropagation]="true".
Custom Element Target
Khi truyền [elementHandleClick], directive sẽ gắn fromEvent lên element đó thay vì elementRef.nativeElement. Điều này cho phép directive host có thể là một element ẩn (class="hidden"), trong khi thực sự lắng nghe click trên một element hiển thị khác.
RxJS & Memory Safety
Directive sử dụng fromEvent() từ RxJS kết hợp với takeUntilDestroyed(destroyRef). Subscription tự động bị hủy khi directive bị destroy, không cần ngOnDestroy thủ công.
Lưu ý quan trọng
⚠️ Gắn vào AfterViewInit: Directive khởi tạo fromEvent trong ngAfterViewInit, nên element target (elementHandleClick) phải tồn tại trong DOM trước thời điểm này. Không nên truyền element được tạo động sau ngAfterViewInit.
⚠️ stopPropagation trong handler: Khi directive đã tự gọi stopPropagation() (mặc định), handler (outClick) nhận được event đã bị stop propagation. Tuy nhiên, nếu bạn có thêm event binding Angular (click) trên cùng element, Angular's event system vẫn có thể xử lý — hãy kiểm tra kỹ behavior khi dùng kết hợp.
⚠️ Selector dạng attribute: Đây là attribute directive, selector là [libsUiComponentsDeviceSupportHandleClickDirective]. Phải dùng dạng attribute trên element, không dùng dạng tag.
⚠️ window / document làm target: Khi truyền window hoặc document vào [elementHandleClick], directive sẽ lắng nghe click toàn trang. Hãy đảm bảo chỉ có một instance directive làm điều này tại một thời điểm, và component chứa directive phải được destroy đúng cách khi không còn dùng nữa.
