npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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_INJECT cho ả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-file

Import

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

⚠️ isSourceIframe cho Google Drive: Dùng link dạng https://drive.google.com/file/d/{fileId}/preview kết hợp [isSourceIframe]="true". Không dùng link /view thô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ự:

  1. Nếu isSourceIframe = true → dùng url / origin_url làm src trực tiếp (bỏ qua tất cả logic map)
  2. Nếu MIME type thuộc nhóm Microsoft Office → dùng https://view.officeapps.live.com/op/embed.aspx?src=...
  3. 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 indexmodel.required(), phải truyền bằng [(index)] hoặc cả [index] lẫn (indexChange). Không có giá trị mặc định.