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

@eqproject/eqp-attachments

v21.0.0

Published

Dynamic attachments component - Angular Material based

Downloads

570

Readme

Table of Contents

=================

Overview

@eqproject/eqp-attachments provides a complete, client-side solution for attachment management, now fully modernized with the latest Angular features.

Key Features:

  • Single or Multiple Attachments: Easily configure the component to handle one or many attachments.
  • Multiple Sources: Supports local file uploads, direct links from the web, and Dropbox integration.
  • Modern UI: A clean, animated drag-and-drop interface with two layout variants (full and compact).
  • Dual View Mode: Allows users to switch between a responsive Card Grid and a detailed Table View .
  • Image Cropper: A built-in tool for cropping and rotating uploaded images.
  • Client-Side Validation: Set limits on file size, file types, and more.
  • Video Support: Automatic thumbnail generation and optional server-side video compression.
  • Large File Handling: Files exceeding a configurable byte threshold are stored as a native File object instead of Base64, preventing memory issues with large uploads.
  • Extensible Table: Add custom columns and custom "more options" menu actions to the table view.
  • Granular Action Control: Show, hide, or disable any built-in or custom action on a per-row basis via hook functions.
  • Highly Configurable: Almost every label, button, and feature can be customized via inputs.
  • Purely Client-Side: The component manages attachments locally without requiring a backend connection, emitting the final data for you to handle.

Requirements

  • Angular 17+
  • Angular Material installed and configured in your project.
  • Font Awesome for a complete icon set (optional, but recommended for the best visual experience).

Getting Started

Step 1: Install the Package

Shell

npm install --save @eqproject/eqp-attachments

Step 2: Import the Component

The component is standalone , so you can import it directly into your component or module.

TypeScript

// in your-component.ts or app/shared module
import { EqpAttachmentsModule } from '@eqproject/eqp-attachments';

@Component({
  selector: 'my-feature-component',
  standalone: true,
  imports: [ EqpAttachmentsModule ],
  template: `
    <eqp-attachments
      [attachmentsList]="myAttachments"
      (localEditedAttachments)="onAttachmentsChange($event)">
    </eqp-attachments>
  `
})
export class MyFeatureComponent {
  myAttachments: IAttachmentDTO[] = [];

  onAttachmentsChange(updatedList: IAttachmentDTO[]) {
    this.myAttachments = updatedList;
    console.log('Attachments list updated!', this.myAttachments);
  }
}

Step 3: Add Styles (Optional)

For the best icon display, include Font Awesome in your project's styles, for example, in angular.json:

JSON

"styles": [
  "src/styles.scss",
  "node_modules/@fortawesome/fontawesome-free/css/all.min.css"
],

API Reference

Inputs

Core Configuration

| Input | Type | Default | Description | | ---------------------- | -------------------- | ---------------- | ------------------------------------------------------------------------------------------------- | | attachmentsList | IAttachmentDTO[] | null | The array of attachments to display and manage. | | singleAttachment | IAttachmentDTO | null | An alternative to attachmentsList for passing a single attachment object in non-multiple mode. | | multipleAttachment | boolean | true | If true, manages a list of attachments. If false, handles only a single attachment. | | allowedTypes | AttachmentType[] | [FILE, LINK] | An array of allowed attachment sources (e.g., [1, 2, 3] for File, Link, and Dropbox). |


UI & View Configuration

| Input | Type | Default | Description | | ---------------------- | ----------- | ------------- | -------------------------------------------------------------------------------- | | layout | 'full' \| 'compact' | 'compact' | 'full' renders a large centered dropbox area; 'compact' renders a slim inline uploader bar. | | viewMode | 'card' \| 'table' | 'table' | The default display mode for the attachment list. | | chooseView | boolean | true | If true, displays the Card/Table view switcher in the header. | | showHeader | boolean | true | If true, displays the title in the header when multipleAttachment is true. | | showSummary | boolean | false | If true, displays the summary block with file count and total size. | | showUploadTitle | boolean | true | If true, shows the title above the drop area. | | showDropArea | boolean | true | If false, hides the entire upload/drop area (useful for read-only display). | | cardSize | 'small' \| 'medium' \| 'large' \| 'custom' | 'small' | Controls the size of cards in card view. Use 'custom' with customCardWidthPx and customCardHeightPx. | | customCardWidthPx | number | 200 | Sets the custom card width in pixels when cardSize is 'custom'. | | customCardHeightPx | number | 180 | Sets the custom card height in pixels when cardSize is 'custom'. | | showMatCard | boolean | true | If true (and in table mode), the component is rendered inside a mat-card. | | cropDialogClass | string | undefined | Assigns a custom CSS class to the image cropper dialog panel. |


Table View Configuration

| Input | Type | Default | Description | | ---------------------- | ----------- | ------------- | -------------------------------------------------------------------------------- | | customColumns | AttachmentFieldColumn[] | [] | Array to add custom columns to the table view. | | customMenuActions | AttachmentMenuAction[] | [] | Array to add custom actions to the table's "more options" (...) menu. | | hiddenColumns | string[] | [] | An array of column key values to hide from the table view. | | hiddenActions | string[] | [] | An array of action key values to hide from the actions menu (e.g., ['delete']). | | showInlinePreview | boolean | false | If true, shows a thumbnail preview column directly in the table rows. | | isEqpTableMultiLanguage | boolean | false | Enables multilanguage support for the embedded table. | | tablePaginatorVisible | boolean | true | If false, hides the paginator below the table. | | isTableSearcheable | boolean | true | If false, hides the search field above the table. | | tablePaginatorSize | number | null | Sets the default page size for the table paginator. |


Functional Configuration

| Input | Type | Default | Description | | ------------------------------- | -------------------- | ------------- | --------------------------------------------------------------------------------------------------------- | | maxFileSizeMB | number | 500 | Sets the maximum file size in megabytes for each uploaded file. Files exceeding this limit are rejected with a toast notification. | | base64LimitMB | number | 100 | Files exceeding this size (MB) are stored as a native File object in LargeFile instead of being Base64-encoded. See Large File Handling. | | loadMultipleFiles | boolean | false | If true, allows selecting multiple files at once from the file browser. | | singleAttachmentDragAndDrop | boolean | true | If true, enables the drag-and-drop UI even in single-attachment mode. | | showActionButtons | boolean | false | If true, shows Download/Preview/Delete action buttons below the attachment in single-attachment mode. | | isDisabled | boolean | false | Disables all "add attachment" controls. | | disableAction | boolean | false | If true, disables the Delete action in both card and table views. | | allowOnlyImages | boolean | false | If true, restricts file uploads to image types only. | | acceptedFileTypes | string | undefined | A string for the <input> accept attribute (e.g., '.pdf,image/*'). | | showPreview | boolean | true | Enables the preview functionality for attachments. | | enableImageCrop | boolean | true | If false, skips the crop dialog and uploads images directly (compression still applies). | | compressionOptions | IOptions | { maxSizeMB: 0.5, maxWidthOrHeight: 1920, useWebWorker: true } | Options for client-side image compression. | | cropOptions | CropOptionEnum[] | [1, 2] | Enables specific tools in the image cropper (1: Rotate, 2: Flip). Pass [] to show no tools. | | separatedUploadButtons | boolean | false | If true and multiple allowedTypes are available, shows separate buttons instead of a dropdown menu. | | getAttachmentEndpoint | string | null | API endpoint (POST) to fetch full attachment data (including FileDataBase64) for image previews. | | productionBaseUrl | string | null | Base URL used to build a Google Viewer URL for document previews (e.g., PDFs, Word files). | | videoCompression | object | { enabled: false, ... } | Configuration for optional server-side video compression. See Video Support. | | actionHiddenFn | (actionKey: string, att?: IAttachmentDTO) => boolean | undefined | A global hook to dynamically hide any action (built-in or custom) for a specific attachment. See Dynamic Action Control. | | actionDisabledFn | (actionKey: string, att?: IAttachmentDTO) => boolean | undefined | A global hook to dynamically disable any action for a specific attachment. |


Labels & Text

| Input | Default | | --------------------------------- | --------------------------------------------- | | uploadTitle | 'Upload file' | | uploadSubtitle | 'Drag & drop files or click' | | dropHereLabel | 'Drop files here' | | supportedFormatsLabel | 'Supported formats: JPEG, PNG, PDF...' | | browseFilesLabel | 'Browse files' | | uploadSummaryLabel | 'Attachments List' | | filesLabel | 'Files' | | totalSizeLabel | 'Total Size' | | emptyTableMessage | 'No data found' | | emptyStateLabel | 'No files have been uploaded' | | openLinkLabel | "Open link" | | addButtonLabel | "Add" | | downloadLabel | "Download" | | deleteLabel | "Delete" | | previewLabel | "Preview" | | confirmLabel | "Confirm" | | abortLabel | "Cancel" | | saveLabel | "Save" | | exitLabel | "Exit" | | fileNameLabel | "File name" | | uploadFileLabel | "Upload file" | | uploadWithDropboxLabel | "Upload with Dropbox" | | cropLabel | "Choose the image dimensions" | | removedLabel | 'File removed' | | addedSuccessfullyLabel | 'file(s) uploaded successfully.' | | deleteDialogTitle | null | | deleteDialogMessage | 'Are you sure you want to delete...' | | noImageSelectedErrorMessage | 'You cannot select a file that is not...' | | wrongTypeSelectedErrorMessage | 'The selected file cannot be uploaded.' | | videoPreviewErrorMessage | 'Cannot open a preview of a video file.' | | audioPreviewErrorMessage | 'Cannot open a preview of an audio file.' | | flipHorinzontalLabel | 'Flip horizontally' | | flipVerticalLabel | 'Flip vertically' | | rotateRightLabel | 'Rotate right' | | rotateLeftLabel | 'Rotate left' | | eqpTableSearchText | 'Search' | | downloadTooltipPosition | 'below' |

Outputs

| Output | Event Arguments | Description | | -------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | (localEditedAttachments) | IAttachmentDTO[] | Emits the complete, updated list of attachments whenever a file/link is added or removed. This is the primary output to listen to. | | (downloadAttachment) | IAttachmentDTO | Triggered on a download attempt for an attachment missing its FileDataBase64, allowing the parent component to fetch the data. | | (onDeleteAttachment) | IAttachmentDTO | Emits the attachment object just before it is removed from the list. | | (abortAddAttachment) | void | Fired when the user cancels an action from a modal dialog (e.g., the crop dialog). |

Data Models

IAttachmentDTO Interface

| Property | Type | Description | | ----------------------- | ------------------- | ------------------------------------------------------ | | ID | number \| string | Unique ID of the attachment. | | FileName | string | Name of the file or link. | | FilePath | string | Path of the link or the file on the server. | | FileExtension | string | The file extension (e.g., 'pdf', 'png'). | | AttachmentType | AttachmentType | The type of attachment (1: FILE, 2: LINK, 3: DROPBOX). | | FileDataBase64 | string | The Base64 content of the file. null for large files (see LargeFile). | | FileContentType | string | The MIME type of the file (e.g., image/png). | | IsImage | boolean | true if the attachment is an image. | | FileThumbnailBase64 | string | The Base64 content of a low-resolution thumbnail (auto-generated for videos). | | LargeFile | File | The native browser File object for files exceeding base64LimitMB. Used instead of FileDataBase64. | | IsLargeFile | boolean | true when the attachment is stored in LargeFile rather than FileDataBase64. | | TrustedUrl | any | (Internal State) A SafeResourceUrl used for previewing links, PDFs, and videos in the dialog. | | isUploading | boolean | (Internal State) true during the upload process. |


AttachmentFieldColumn Interface

| Property | Type | Description | | ----------------------- | ------------------- | ------------------------------------------------------ | | key | string | Unique identifier for the column. Used by hiddenColumns. | | display | string | The column header label to display. | | type | TypeAttachmentColumn | The rendering type (TEXT, DATE, TEMPLATE). Defaults to TEXT. | | externalTemplate | TemplateRef<any> | The ng-template to use (required if type is TEMPLATE). | | styles | { flex: string } | Defines the column width using CSS flex shorthand (e.g., '1 1 0%' or '0 0 150px'). | | position | number | The column's display order. The built-in "File" column is 10, "Actions" is 100. | | class | string | A custom CSS class added to every cell in this column. | | hidden | boolean \| (() => boolean) | If true (or if the function returns true), the column is excluded from rendering. |


AttachmentMenuAction Interface

| Property | Type | Description | | ----------------------- | ------------------- | ------------------------------------------------------ | | key | string | Optional unique identifier for the action. Used by hiddenActions and actionHiddenFn/actionDisabledFn. | | icon | string | mat-icon name to display (e.g., 'share'). | | name | string | Text to show in the menu item. | | fn | (att: IAttachmentDTO) => void | Function to execute when the item is clicked. | | disabled | (att: IAttachmentDTO) => boolean | Function to dynamically disable the action for a specific row. | | hidden | (att: IAttachmentDTO) => boolean | Function to dynamically hide the action for a specific row. | | position | number | Display order. Built-in "Preview" is 10, "Delete" is 100. Use a number in between to insert a custom action. |


AttachmentCardSize Type

export type AttachmentCardSize = 'small' | 'medium' | 'large' | 'custom';



Layout Configuration

export type Layout = 'full' | 'compact';


Advanced Features

Large File Handling

By default, files are read as Base64 strings and stored in IAttachmentDTO.FileDataBase64. However, for files exceeding the base64LimitMB threshold (default: 100 MB), the component switches strategy to avoid memory exhaustion: the native browser File object is stored in IAttachmentDTO.LargeFile, and FileDataBase64 is set to null.

When localEditedAttachments emits a list containing large files, your backend integration must handle them differently (e.g., via FormData multipart upload).

onAttachmentsChange(list: IAttachmentDTO[]) {
  for (const att of list) {
    if (att.IsLargeFile && att.LargeFile) {
      // Handle with multipart/form-data upload
      const formData = new FormData();
      formData.append('file', att.LargeFile, att.FileName);
      this.http.post('/api/upload', formData).subscribe();
    } else {
      // Standard Base64 handling
    }
  }
}

Note: Video files are always treated as large files regardless of size — they are stored directly in LargeFile and a Base64 thumbnail is generated and stored in FileThumbnailBase64.


Video Support

The component has first-class support for video attachments:

  • Thumbnail generation: When a video is selected, the component automatically captures a frame at the 1-second mark and stores it as a JPEG Base64 string in FileThumbnailBase64. This thumbnail is displayed in both card and table views.
  • In-browser preview: Videos stored in LargeFile can be played directly in the preview dialog using the native HTML5 <video> player.
  • Server-side compression (optional): Pass the videoCompression input to enable compression via an external API endpoint.
<eqp-attachments
  [attachmentsList]="videoList"
  [videoCompression]="compressionConfig"
  (localEditedAttachments)="onAttachmentsChange($event)">
</eqp-attachments>
compressionConfig = {
  enabled: true,
  maxWidth: 1280,  // Max output width in px
  crf: 23,         // Constant Rate Factor (quality, lower = better)
  preset: 'veryfast',
  maxFps: 30,
  audioBitrate: 128000
};

The compression call is a multipart/form-data POST to the URL configured inside the component. The response is expected to be a Blob (the compressed .mp4 file).


Dynamic Action Control

For fine-grained control over which actions are available for each attachment in the table view, use the actionHiddenFn and actionDisabledFn hook inputs. These are called for every action (both built-in and custom) on every row render.

Built-in action keys: 'preview', 'delete'. Custom actions use the key field you define in AttachmentMenuAction.

<eqp-attachments
  [attachmentsList]="documents"
  [actionHiddenFn]="myHiddenFn"
  [actionDisabledFn]="myDisabledFn"
  (localEditedAttachments)="onAttachmentsChange($event)">
</eqp-attachments>
// Hide the 'delete' action for already-persisted attachments (those with a numeric ID)
myHiddenFn = (actionKey: string, att: IAttachmentDTO): boolean => {
  if (actionKey === 'delete' && typeof att.ID === 'number' && att.ID > 0) {
    return true;
  }
  return false;
};

// Disable the 'preview' action for attachments without local data
myDisabledFn = (actionKey: string, att: IAttachmentDTO): boolean => {
  if (actionKey === 'preview') {
    return !att.FileDataBase64 && !att.LargeFile;
  }
  return false;
};

You can also hide actions by key name using the simpler hiddenActions array input, which is evaluated before the hook:

<!-- Always hide the 'delete' button for everyone -->
<eqp-attachments [hiddenActions]="['delete']" ...></eqp-attachments>

Theming with CSS Custom Properties

The component exposes a set of CSS custom properties on :host that you can override from the parent application to match your design system.

// In your component or global styles
eqp-attachments {
  --primary-color: #0d6efd;          // Main accent color (buttons, borders, icons)
  --success-color: #198754;          // Toast success / progress fill
  --error-color: #dc3545;            // Toast error color
  --background-light: #f8f9fa;       // Page background tint
  --background-card: #ffffff;        // Card and panel background
  --text-color: #212529;             // Primary text
  --text-color-light: #6c757d;       // Secondary/muted text
  --border-color: rgba(0,0,0,0.12);  // Dividers and card borders
  --shadow-color: rgba(13,110,253,0.15); // Box shadows
  --border-radius: 12px;             // Card and dialog corner radius
  --transition-speed: 0.25s;         // Animation duration
}

Use Cases

Case 1: Single Image Upload

A configuration for uploading a single image, with drag-and-drop and the image cropper enabled.

HTML

<eqp-attachments
  [multipleAttachment]="false"
  [attachmentsList]="singleImage"
  [singleAttachmentDragAndDrop]="true"
  [allowOnlyImages]="true"
  [cropOptions]="[1, 2]"
  [maxFileSizeMB]="10"
  (localEditedAttachments)="onAttachmentsChange($event)">
</eqp-attachments>

TypeScript

import { IAttachmentDTO } from '@eqproject/eqp-attachments';

export class MyComponent {
  singleImage: IAttachmentDTO[] = [];

  onAttachmentsChange(updatedList: IAttachmentDTO[]) {
    // In single mode, the list will contain 0 or 1 item
    this.singleImage = updatedList;
  }
}

Case 2: Multiple Attachments Management

A full-featured configuration for managing multiple types of attachments, starting in card view and allowing the user to switch to table view.

HTML

<eqp-attachments
  [multipleAttachment]="true"
  [attachmentsList]="documentList"
  [allowedTypes]="[1, 2]"
  [viewMode]="'card'"
  [headerTitle]="'Project Documents'"
  [showSummary]="true"
  (localEditedAttachments)="onAttachmentsChange($event)">
</eqp-attachments>

TypeScript

import { IAttachmentDTO, AttachmentType } from '@eqproject/eqp-attachments';

export class MyComponent {
  documentList: IAttachmentDTO[] = [
    {
      ID: 101,
      IsImage: false,
      AttachmentType: AttachmentType.FILE,
      FileName: "Final Report.pdf",
      FileExtension: "pdf",
    },
    {
      ID: 102,
      IsImage: false,
      AttachmentType: AttachmentType.LINK,
      FileName: "GitHub Repository",
      FilePath: "https://github.com/...",
    },
  ];

  onAttachmentsChange(updatedList: IAttachmentDTO[]) {
    this.documentList = updatedList;
  }
}

Case 3: Custom Columns

HTML

<eqp-attachments
  [multipleAttachment]="true"
  [attachmentsList]="documentList"
  [allowedTypes]="[1, 2]"
  [viewMode]="'table'"
  [headerTitle]="'Project Documents'"
  [showSummary]="true"
  (localEditedAttachments)="onAttachmentsChange($event)"
  [customColumns]="myCustomColumns"
  [customMenuActions]="myCustomActions">
</eqp-attachments>

<ng-template #statusTemplate let-att>
  <mat-chip-listbox>
    <mat-chip [style.background-color]="att.Status === 'Approved' ? '#c8e6c9' : '#ffcdd2'"
              [style.color]="att.Status === 'Approved' ? '#2e7d32' : '#c62828'">
      {{ att.Status }}
    </mat-chip>
  </mat-chip-listbox>
</ng-template>

TypeScript

@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<any>;

  documentList: IAttachmentDTO[] = [
    {
      ID: 1,
      FileName: 'report.pdf',
      FileExtension: 'pdf',
      UploadUserName: 'Mario Rossi',
      Status: 'Approved'
    },
    {
      ID: 2,
      FileName: 'schema.zip',
      FileExtension: 'zip',
      UploadUserName: 'Laura Bianchi',
      Status: 'Pending'
    }
  ];
  myCustomColumns: AttachmentFieldColumn[] = [];
  myCustomActions: AttachmentMenuAction[] = [];

  ngOnInit() {
    this.myCustomColumns = [
      {
        key: 'UploadUserName',
        display: 'Uploaded By',        // 'display' is the header label
        type: TypeAttachmentColumn.TEXT, // Renders as plain text
        styles: { flex: '2 1 0%' }, // Takes 2 parts of the available space
        position: 20 // Shows after "File" (10)
      },
      {
        key: 'Status',
        display: 'Status',
        type: TypeAttachmentColumn.TEMPLATE,
        externalTemplate: this.statusTemplate, // Pass the ng-template
        styles: { flex: '1 1 0%' },
        position: 30 // Shows after "Uploaded By" (20)
      }
    ];

    this.myCustomActions = [
      {
        key: 'share',           // Optional but recommended for hiddenActions/hooks
        icon: 'share',
        name: 'Share',          // 'name' is the label shown in the menu
        fn: (item) => this.shareAttachment(item),  // 'fn' is the click handler
        position: 25 // Show after "Preview" (10) and before "Delete" (100)
      },
      {
        key: 'rename',
        icon: 'edit',
        name: 'Edit Name',
        fn: (item) => this.renameAttachment(item),
        position: 26
      }
    ];
  }

Credits

This library has been developed by EqProject SRL. For more info, contact: [email protected]