@eqproject/eqp-attachments
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-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
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: info@eqproject.it