@libs-ui/components-preview-file
v0.2.357-31
Published
> Component modal xem trước tệp tin hỗ trợ đa định dạng: hình ảnh, PDF, tài liệu Office — tự động chọn viewer phù hợp (Google Viewer, Microsoft Online Viewer, hoặc iframe trực tiếp).
Readme
@libs-ui/components-preview-file
Component modal xem trước tệp tin hỗ trợ đa định dạng: hình ảnh, PDF, tài liệu Office — tự động chọn viewer phù hợp (Google Viewer, Microsoft Online Viewer, hoặc iframe trực tiếp).
Giới thiệu
LibsUiComponentsPreviewFileComponent cung cấp giao diện modal để xem nhanh nội dung tệp tin mà không cần tải về. Component tự động nhận diện MIME type để định tuyến sang viewer tối ưu: hình ảnh hiển thị trực tiếp, tài liệu Office dùng Microsoft Online Viewer, PDF và các loại khác dùng Google Docs Viewer. Hỗ trợ điều hướng qua danh sách nhiều tệp bằng nút Prev/Next và cho phép thêm các nút hành động tùy chỉnh trên thanh tiêu đề.
Tính năng
- ✅ Hỗ trợ đa định dạng: Hình ảnh (JPEG, PNG, GIF, SVG), PDF, Word (DOC/DOCX), Excel (XLS/XLSX), PowerPoint (PPT/PPTX)
- ✅ Tự động chọn Viewer: Microsoft Online Viewer cho Office, Google Docs Viewer cho PDF/các định dạng khác
- ✅ Chế độ Iframe trực tiếp: Bỏ qua map viewer — dùng URL gốc làm src iframe (cho Google Drive, Dropbox share link)
- ✅ Điều hướng Prev/Next: Duyệt qua danh sách nhiều tệp với two-way binding index
- ✅ Nút hành động tùy chỉnh: Thêm Download, Share hoặc bất kỳ action nào lên header modal
- ✅ Loading state: Spinner cho hình ảnh, Skeleton cho iframe — reset tự động khi chuyển tệp
- ✅ Fallback ảnh lỗi: Hỗ trợ injection token
LINK_IMAGE_ERROR_TOKEN_INJECTcho ảnh placeholder khi lỗi load - ✅ OnPush Change Detection + Angular Signals
Khi nào sử dụng
- Xem nhanh nội dung tệp đính kèm (hợp đồng, báo cáo, ảnh sản phẩm) mà không cần tải về máy
- Duyệt gallery ảnh hoặc danh sách tài liệu PDF/Office trong workflow phê duyệt
- Nhúng preview tệp từ Google Drive, Dropbox hoặc CDN nội bộ vào trong ứng dụng
- Cho phép người dùng thực hiện hành động (download, share) ngay từ giao diện xem trước
Cài đặt
npm install @libs-ui/components-preview-fileImport
import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
@Component({
standalone: true,
imports: [LibsUiComponentsPreviewFileComponent],
// ...
})
export class YourComponent {}Ví dụ sử dụng
Ví dụ 1 — Sử dụng cơ bản
Mở trình xem trước tệp với các thiết lập mặc định.
HTML:
<button (click)="openPreview(0)" class="libs-ui-button-primary">
Xem danh sách tệp
</button>
@if (showPreview()) {
<libs_ui-components-preview_file
[data]="fileList"
[(index)]="currentIndex"
(outClose)="showPreview.set(false)" />
}TypeScript:
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
import { IFile } from '@libs-ui/interfaces-types';
@Component({
standalone: true,
imports: [LibsUiComponentsPreviewFileComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
// ...
})
export class YourComponent {
// [data]="fileList" — danh sách tệp cần xem trước (Bắt buộc)
readonly fileList: IFile[] = [
{ name: 'Beautiful Landscape.jpg', url: 'https://images.unsplash.com/photo-xxx.jpg', mimetype: 'image/jpeg' },
{ name: 'Sample PDF Document.pdf', url: 'https://example.com/dummy.pdf', mimetype: 'application/pdf' },
{ name: 'Project Plan Excel.xlsx', url: 'https://example.com/plan.xlsx', mimetype: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
{ name: 'Sample Presentation.pptx', url: 'https://example.com/deck.pptx', mimetype: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' },
];
// [(index)]="currentIndex" — chỉ số tệp đang xem (two-way binding)
readonly currentIndex = signal(0);
// @if (showPreview()) — điều kiện hiển thị component
readonly showPreview = signal(false);
// Handler mở preview tại vị trí index — gọi từ nút "Xem danh sách tệp"
openPreview(index: number): void {
this.currentIndex.set(index);
this.showPreview.set(true);
}
}Ví dụ 2 — Tùy biến nút hành động
Thêm các nút chức năng như Tải xuống, Chia sẻ ngay trong giao diện xem trước.
HTML:
<!-- Danh sách thumbnail -->
<div class="flex gap-4">
@for (file of fileList; track file.url) {
<div (click)="openPreview($index)" class="cursor-pointer">
{{ file.name }}
</div>
}
</div>
@if (showPreview()) {
<libs_ui-components-preview_file
[data]="fileList"
[(index)]="currentIndex"
[buttons]="customButtons"
(outClose)="showPreview.set(false)" />
}TypeScript:
import { IButton } from '@libs-ui/components-buttons-button';
// fileList / currentIndex / showPreview / openPreview() — xem Ví dụ 1 phía trên.
// [buttons]="customButtons" — thêm nút hành động tùy biến trên header preview
readonly customButtons: IButton[] = [
{
key: 'download',
label: 'Tải xuống',
type: 'button-primary',
classIconLeft: 'libs-ui-icon-download-outline',
action: async (index: number) => {
alert(`Bắt đầu tải xuống: ${this.fileList[index].name}`);
},
},
{
key: 'share',
label: '',
type: 'button-outline',
classIconLeft: 'libs-ui-icon-upload-outline',
iconOnlyType: true,
action: async (index: number) => {
alert(`Chia sẻ tệp: ${this.fileList[index].name}`);
},
},
];Ví dụ 3 — Nguồn Iframe trực tiếp (Google Drive, Dropbox)
Có thể bỏ qua map URL viewer, dùng luôn link cho iframe (VD: Share link public của Google Drive). Không cần qua Google Docs Viewer hay Microsoft Viewer.
HTML:
<button (click)="openPreviewIframe(0)" class="libs-ui-button-primary">
Mở Link Dạng Iframe
</button>
@if (showPreviewIframe()) {
<libs_ui-components-preview_file
[data]="fileListIframe"
[(index)]="currentIframeIndex"
[isSourceIframe]="true"
(outClose)="showPreviewIframe.set(false)" />
}TypeScript:
import { signal } from '@angular/core';
import { IFile } from '@libs-ui/interfaces-types';
// [data]="fileListIframe" — dùng link share public (VD: Google Drive) render trực tiếp qua iframe
readonly fileListIframe: IFile[] = [
{
name: 'Google Drive Source',
url: 'https://drive.google.com/file/d/1B9Ke75VYIznY4cjyZTnXRsRYPkmJ1AgK/preview',
mimetype: 'application/pdf',
},
];
// [(index)]="currentIframeIndex" / @if (showPreviewIframe())
readonly currentIframeIndex = signal(0);
readonly showPreviewIframe = signal(false);
// Handler mở preview iframe — gọi từ nút "Mở Link Dạng Iframe"
openPreviewIframe(index: number): void {
this.currentIframeIndex.set(index);
this.showPreviewIframe.set(true);
}⚠️
isSourceIframecho Google Drive: Dùng link dạnghttps://drive.google.com/file/d/{fileId}/previewkết hợp[isSourceIframe]="true". Không dùng link/viewthông thường vì Google Drive chặn nhúng iframe.
Ví dụ 4 — Với LINK_IMAGE_ERROR_TOKEN_INJECT (ảnh placeholder khi lỗi)
import { Component } from '@angular/core';
import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
import { LINK_IMAGE_ERROR_TOKEN_INJECT } from '@libs-ui/utils';
@Component({
// ...
imports: [LibsUiComponentsPreviewFileComponent],
providers: [
{
provide: LINK_IMAGE_ERROR_TOKEN_INJECT,
useValue: 'https://example.com/assets/image-error-placeholder.png',
},
],
})
export class AppComponent {}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [data] | IFile[] | Bắt buộc | Danh sách tệp cần xem trước. Phải là mảng IFile[]. | [data]="fileList" |
| [(index)] | number | Bắt buộc | Index tệp hiện tại trong mảng data. Hỗ trợ two-way binding (model). | [(index)]="currentIndex" |
| [buttons] | IButton[] | undefined | Danh sách nút hành động tùy chỉnh hiển thị trên header modal (Download, Share...). | [buttons]="customButtons" |
| [zIndex] | number | undefined | Z-index CSS cho modal. Dùng khi cần kiểm soát lớp hiển thị. | [zIndex]="1050" |
| [isSourceIframe] | boolean | undefined (falsy) | Bỏ qua logic map viewer — dùng url hoặc origin_url trực tiếp làm src của iframe. Dùng cho Google Drive share link, Dropbox, v.v. | [isSourceIframe]="true" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outClose) | void | Phát ra khi người dùng đóng modal (nhấn nút X hoặc click backdrop). | handlerClosePreview(): void { this.showPreview.set(false); } | (outClose)="handlerClosePreview()" |
| (outChangeIndex) | number | Phát ra index mới sau khi nhấn Prev/Next để chuyển tệp. | handlerChangeIndex(index: number): void { console.log(index); } | (outChangeIndex)="handlerChangeIndex($event)" |
Types & Interfaces
Các type được import từ @libs-ui/interfaces-types:
import { IFile } from '@libs-ui/interfaces-types';
import { IButton } from '@libs-ui/components-buttons-button';export interface IFile {
id?: string;
name?: string; // Tên tệp hiển thị trên header modal
file?: File; // Native File object (dùng khi upload)
size?: string; // Kích thước tệp (chuỗi, vd: "2.5MB")
isUploading?: boolean;
percentUploading?: number;
isUpdate?: boolean;
url?: string; // URL công khai để hiển thị tệp
origin_url?: string; // URL gốc (fallback khi url không có)
mimetype?: string; // MIME type — dùng để chọn viewer phù hợp
type?: 'document' | 'image' | 'video' | 'audio';
error?: string;
isAvatar?: boolean;
}
export interface IButton {
key: string; // ID duy nhất cho button
label?: string; // Text hiển thị
type?: string; // Kiểu button (button-primary, button-outline...)
classIconLeft?: string; // Class icon bên trái
classIconRight?: string; // Class icon bên phải
iconOnlyType?: boolean; // Chỉ hiện icon, ẩn label
classInclude?: string; // Class CSS bổ sung
action?: (index: number) => Promise<void>; // Callback nhận index tệp hiện tại
}Logic ẩn quan trọng
1. Tự động chọn Viewer theo MIME Type
Component ưu tiên theo thứ tự:
- Nếu
isSourceIframe = true→ dùngurl/origin_urllàm src trực tiếp (bỏ qua tất cả logic map) - Nếu MIME type thuộc nhóm Microsoft Office → dùng
https://view.officeapps.live.com/op/embed.aspx?src=... - Mọi trường hợp còn lại (PDF, ảnh embed, v.v.) → dùng
https://docs.google.com/viewer?url=...
Các MIME type được nhận diện là Office (dùng Microsoft Viewer):
| MIME Type | Loại tệp |
|---|---|
| application/msword | Word DOC |
| application/vnd.openxmlformats-officedocument.wordprocessingml.document | Word DOCX |
| application/vnd.openxmlformats-officedocument.wordprocessingml.template | Word DOTX |
| application/vnd.ms-excel | Excel XLS |
| application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Excel XLSX |
| application/vnd.openxmlformats-officedocument.spreadsheetml.template | Excel XLTX |
| application/vnd.ms-excel.sheet.macroEnabled.12 | Excel XLSM |
| application/vnd.ms-powerpoint | PowerPoint PPT |
| application/vnd.openxmlformats-officedocument.presentationml.presentation | PowerPoint PPTX |
| application/vnd.ms-powerpoint.presentation.macroEnabled.12 | PowerPoint PPTM |
| application/vnd.openxmlformats-officedocument.presentationml.slideshow | PowerPoint PPSX |
2. Kiểm tra hình ảnh bằng Pipe
Hình ảnh được render bằng thẻ <img> (không qua iframe) khi LibsUiPipesCheckFileExtensionPipe trả về true cho loại 'image'. Pipe này kiểm tra cả mimetype lẫn extension từ tên tệp.
3. Reset loading khi chuyển tệp
Khi index thay đổi, computed() tính lại fileView và dùng untracked() để set loading = true mà không tạo vòng lặp reactive. Trạng thái loading chỉ về false sau khi (load) event của <img> hoặc <iframe> kích hoạt.
4. Bảo vệ điều hướng biên
Nút Prev chỉ hiển thị khi index() > 0. Nút Next chỉ hiển thị khi index() < data().length - 1. Component không tự động wrap vòng (không nhảy từ cuối về đầu).
Lưu ý quan trọng
⚠️ Yêu cầu kết nối Internet: Google Viewer và Microsoft Online Viewer là dịch vụ bên ngoài. Tệp phải có URL công khai (public URL) để các viewer này truy cập được. Không hoạt động với URL nội bộ hoặc localhost.
⚠️ CORS và embed permissions: Một số máy chủ cấu hình header X-Frame-Options: DENY hoặc Content-Security-Policy chặn nhúng iframe. Kiểm tra cấu hình server nếu tệp không hiển thị được.
⚠️ Chỉ dùng HTTPS: Trình duyệt sẽ chặn nội dung Mixed Content nếu trang web dùng HTTPS nhưng URL tệp là HTTP. Luôn dùng link HTTPS cho tệp cần preview.
⚠️ isSourceIframe cho Google Drive: Khi chia sẻ file từ Google Drive, dùng link dạng https://drive.google.com/file/d/{fileId}/preview kết hợp với [isSourceIframe]="true". Không dùng link /view thông thường vì Google Drive chặn nhúng iframe.
⚠️ Two-way binding bắt buộc cho index: Input index là model.required(), phải truyền bằng [(index)] hoặc cả [index] lẫn (indexChange). Không có giá trị mặc định.
