@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 (
fullandcompact). - 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
Fileobject 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-attachmentsStep 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
LargeFileand a Base64 thumbnail is generated and stored inFileThumbnailBase64.
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
LargeFilecan be played directly in the preview dialog using the native HTML5<video>player. - Server-side compression (optional): Pass the
videoCompressioninput 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]
