Add Custom Feature for Attaching Files to PDFs in Angular PDF Viewer?
Description:
This article demonstrates how to implement custom functionality in Angular PDF Viewer that allows users to attach additional files directly to PDF documents. The guide outlines the steps, tools, and best practices to seamlessly integrate this feature.
Solution:
To add the feature of attaching files to PDFs in the Angular PDF Viewer, we’ll create a custom toolbar and a sidebar to manage the list of attached files. The sidebar will display attached files with options to view or delete them. Additionally, we will showcase a demo to highlight this functionality, accompanied by detailed documentation for easy integration.
Code Snippets:
Client-Side Code Block:
app.component.ts:
In the code block below, we have initialized the PDF Viewer component along with a sidebar to display the details of the attached files. The sidebar also includes an option to delete any files that are no longer needed.
<div class="content-wrapper" style="display: abosolute;width: 80%;height: 670px">
<ejs-pdfviewer id="pdfViewer"
[resourceUrl]='resource'
[toolbarSettings]="toolbarSettings"
(toolbarClick)="toolbarClick($event)"
(documentLoad)='documentLoaded($event)'
[documentPath]='document'
style="height:740px;width:display:block">
</ejs-pdfviewer>
</div>
<div class="attachment-side-bar-content" style="display: absolute; width: 20%;">
<ejs-sidebar id="defaultSidebar" #sidebar cssClass="e-outline" class="e-pv-e-attachment-sidebar" width="20%" position="Right" [enableGestures]="false">
<div style="font-weight: 500; font-size: 16px; height: 24px; margin: 15px 10px 5px;">
PDF Attachments Details
</div>
<div *ngFor="let file of uploadedFiles; let i = index" class="attachemnt_details" style="flex: 1; padding: 20px; border: 2px solid #007bff; border-radius: 5px; margin-bottom: 10px;">
<button class="dropdown-btn" (click)="toggleDropdown(i)">⋮</button>
<div class="dropdown-content" [class.show]="dropdownOpen[i]">
<a href="#" (click)="deleteFile(i)">Delete</a>
</div>
<h3>Attached File Details</h3>
<p>File Name: {{ file.name }}</p>
</div>
</ejs-sidebar>
</div>
In the code block below, we have implemented a click event to close the delete menu that is opened from the sidebar used for attaching files.
ngOnInit() {
document.addEventListener('click', this.handleClickOutside.bind(this));
}
ngOnDestroy() {
document.removeEventListener('click', this.handleClickOutside.bind(this));
}
handleClickOutside(event: Event) {
const target = event.target as HTMLElement;
if (!target.closest('.dropdown-btn') && !target.closest('.dropdown-content')) {
this.dropdownOpen = this.dropdownOpen.map(() => false);
}
}
public attachPdf: CustomToolbarItemModel = {
prefixIcon: 'e-icons e-link',
id: 'attach_pdf',
align: 'Right',
text: 'Attach PDF',
tooltipText: 'Attach PDF file',
};
toggleDropdown(index: number) {
this.dropdownOpen = this.dropdownOpen.map((open, i) => i === index ? !open : false);
}
public download: CustomToolbarItemModel = {
prefixIcon: 'e-pv-download-document-icon',
id: 'download',
align: 'Right',
tooltipText: 'Download file',
};
In the code block below, we have provided an option to select the file to be attached to the PDF, which will then be stored in a collection. Additionally, the file details will be updated in the sidebar of the PDF Viewer.
private browseFile(): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.gif';
input.onchange = (event: any) => {
const file = event.target.files[0];
if (file) {
this.uploadedFiles.push({ name: file.name });
this.dropdownOpen.push(false);
const reader = new FileReader();
reader.onload = (args: any) => {
var base64String = args.target.result.split('base64,')[1];
this.attachedDocuments.push({ name: file.name, base64: base64String, type: file.type });
};
reader.readAsDataURL(file);
}
};
input.click();
}
The code block below is used to delete a file attached by the user. It also includes a custom toolbar click event to handle retrieving the file and saving the PDF file with the attached files.
deleteFile(index: number): void {
this.uploadedFiles.splice(index, 1);
this.attachedDocuments.splice(index, 1);
this.dropdownOpen.splice(index, 1);
}
public toolbarClick(args: ClickEventArgs): void {
if (args.item && args.item.id === 'attach_pdf') {
this.browseFile();
} else if (args.item && args.item.id === 'download') {
this.processDocuments();
}
}
The code below saves the PDF file with the attached files into the main PDF loaded in the PDF Viewer. It achieves this by sending the main PDF file and the attached files to the service, where the files are merged into the PDF. The updated PDF is then returned as a Base64 string to the client and saved to the local folder.
processDocuments(): void {
var viewer = (<any>document.getElementById('pdfViewer')).ej2_instances[0];
viewer.saveAsBlob().then((value: Blob) => {
const reader = new FileReader();
reader.onload = () => {
const uint8Array = new Uint8Array(reader.result as ArrayBuffer);
let binary = '';
uint8Array.forEach((byte) => (binary += String.fromCharCode(byte)));
const base64String = window.btoa(binary);
const requestBody = {
attachedDocuments: this.attachedDocuments,
PrimaryDocument: base64String
};
this.http.post('https://localhost:7237/pdfviewer/AttachSavePdf', requestBody)
.subscribe((response: any) => {
const mergedDocumentBase64 = response.attachedPDF;
this.downloadBase64File(mergedDocumentBase64, 'merged_document.pdf');
}, error => {
console.error('Error processing documents', error);
});
};
reader.readAsArrayBuffer(value);
});
}
downloadBase64File(base64Data: string, fileName: string): void {
const linkSource = `data:application/pdf;base64,${base64Data}`;
const downloadLink = document.createElement('a');
downloadLink.href = linkSource;
downloadLink.download = fileName;
downloadLink.click();
}
The sample code below retrieves PDF file details from the service, checking whether the PDF file contains any attachments. If attachments are present, their details are fetched from the service and displayed in the sidebar.
public documentLoaded(e: LoadEventArgs): void {
var viewer = (<any>document.getElementById('pdfViewer')).ej2_instances[0];
viewer.saveAsBlob().then((value: Blob) => {
const reader = new FileReader();
reader.onload = () => {
const uint8Array = new Uint8Array(reader.result as ArrayBuffer);
let binary = '';
uint8Array.forEach((byte) => (binary += String.fromCharCode(byte)));
const base64String = window.btoa(binary);
const requestBody = {
attachedDocuments: this.attachedDocuments,
PrimaryDocument: base64String
};
this.http.post('https://localhost:7237/pdfviewer/LoadedPdf', requestBody)
.subscribe((response: any) => {
this.attachedDocuments = response.documentCollections;
if(response.documentCollections.length > 0) {
for (let i = 0; i < response.documentCollections.length; i++) {
this.uploadedFiles.push({
name: response.documentCollections[i].name
});
this.dropdownOpen.push(false);
}
}
}, error => {
console.error('Error loading document', error);
});
};
reader.readAsArrayBuffer(value);
});
}
Server-Side Code Block:
PdfViewerController.cs:
The code block below retrieves the loaded file details from the client and checks if the PDF contains any attachments. If attachments are found, their details are sent to the client and displayed in the sidebar of the PDF Viewer.
public IActionResult LoadedPdf([FromBody] DocumentRequest jsonObject)
{
try
{
byte[] loadedDocumentBytes = Convert.FromBase64String(jsonObject.PrimaryDocument);
using (MemoryStream loadedStream = new MemoryStream(loadedDocumentBytes))
{
PdfLoadedDocument loadedPdf = new PdfLoadedDocument(loadedStream);
List<DocumentInfo> documentCollection = new List<DocumentInfo>();
if (loadedPdf.Attachments != null)
{
foreach (PdfAttachment attachment in loadedPdf.Attachments)
{
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Position = 0;
string mergedBase64 = Convert.ToBase64String(attachment.Data);
documentCollection.Add(new DocumentInfo { Name = attachment.FileName, Base64 = mergedBase64, Type = attachment .MimeType});
}
}
return Ok(new { documentCollections = documentCollection });
}
else { return Ok(new { documentCollections = documentCollection }); }
}
}
catch (Exception ex)
{
return BadRequest(new { Message = ex.Message });
}
}
The code block below retrieves the attached file details and the main PDF file details from the client. It then merges the attachment files into the main PDF, sends the updated PDF details back to the client, and saves the PDF file locally.
public IActionResult AttachSavePdf([FromBody] DocumentRequest jsonObject)
{
try
{
byte[] primaryDocumentBytes = Convert.FromBase64String(jsonObject.PrimaryDocument);
List<(string Name, byte[] Bytes, string Type)> attachedDocuments = new List<(string Name, byte[] Bytes, string Type)>();
foreach (var doc in jsonObject.AttachedDocuments)
{
attachedDocuments.Add((doc.Name, Convert.FromBase64String(doc.Base64), doc.Type));
}
using (MemoryStream primaryStream = new MemoryStream(primaryDocumentBytes))
{
PdfLoadedDocument primaryPdf = new PdfLoadedDocument(primaryStream);
foreach (var attachedDoc in attachedDocuments)
{
using (MemoryStream attachedStream = new MemoryStream(attachedDoc.Bytes))
{
PdfAttachment attachment = new PdfAttachment(attachedDoc.Name, attachedStream);
attachment.ModificationDate = DateTime.Now;
attachment.Description = attachedDoc.Name;
attachment.MimeType = attachedDoc.Type;
if (primaryPdf.Attachments == null)
primaryPdf.CreateAttachment();
primaryPdf.Attachments.Add(attachment);
}
}
using (MemoryStream outputStream = new MemoryStream())
{
primaryPdf.Save(outputStream);
primaryPdf.Close(true);
string mergedBase64 = Convert.ToBase64String(outputStream.ToArray());
return Ok(new { attachedPDF = mergedBase64 });
}
}
}
catch (Exception ex)
{
return BadRequest(new { Message = ex.Message });
}
}
Sample Link:
You can find the full sample in our GitHub repository.
Demo link: Feature Demo
Documentation on code block: Code Implementation Guide
Conclusion:
I hope you enjoyed learning about how to dd custom feature for attaching files to pdfs in Angular PDF Viewer.
You can refer to Angular PDF feature tour page to know about its other groundbreaking feature representations and documentation , and how to quickly get started for configuration specifications.
You can also explore our Angular PDF Viewer Example to understand how to create and manipulate data.
For current customers, you can check out our components from the License and Downloads page. If you are new to Syncfusion, you can try our 30-day free trial to check out our other controls.
If you have any queries or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!