PDF merger with Angular

In this tutorial we are going to introduce you to Angular and showcase how you can build your PDF merger tool, so your documents don't have to leave your laptop to be merged.

In this tutorial we are going to introduce you to Angular, a framework developed by Google. First we are going to start a new project from scratch and then we are going to edit and create some files to build our own PDF Merge Tool. The PDF Merge Tool allows users to upload PDF files, arrange them in the desired order, and then merge them into a single PDF document.

To bootstrap an Angular project from scratch, you need to install the Angular CLI through the terminal first.

sudo npm install -g @angular/cli

The Angular CLI provides various Bash commands for starting, configuring, testing, and running Angular projects.

Angular CLI is not compatible with the pre-installed Node.js version 18.05 in Codesphere. Therefore, you need to change the Node.js version to 18.10 using the following command in the Terminal:

sudo n install 18.10

Once you've completed this, you can go ahead and create a new Angular project using the following command:

ng new pdf-merge

Before the new project is initialized, the terminal will prompt you for user input. You can answer yes to all questions and select CSS in the last question. The project will be initialized afterwards, and all the essential files will be created.

Now that the project is created, you can deploy the Angular template in serve mode directly. To do this, navigate to the project folder using cd and execute the following command in the terminal:

cd pdf-merge/
ng serve --port 3000 --host 0.0.0.0 --public-host XXXXX-3000.2.codesphere.com

t's important to replace "XXXXX" with your workspace ID for this to work. You can find that ID in the URL of your workspace.

Once it's processed, you can deploy, and you will see the Angular default template in your browser's newly opened window.

Now you can start coding. Whenever a file is edited and saved with Ctrl + S, all changes will be immediately reflected in the browser.

We need to install two new libraries in our PDF Merge Tool project. Open a new terminal, navigate to the project folder using cd, and enter the following commands:

cd pdf-merge/
npm install @angular/cdk && npm install pdf-lib

pdf-lib is a library for processing PDFs, and @angular/cdk is a library that allows you to create interactive drag-and-drop lists, among other things. Both libraries have documentation available online.

Now that the dependencies are installed, we can start using the code. We will now go through each file that needs to be modified or created. It's not complicated once you know where to make changes for each modification. So let's get started.

The folder that is relevant to us is the src folder. It contains the source code that we create ourselves. When you open this folder, you will see, among other files, the style.css file. In a CSS file, you can adjust the design settings. You can copy the following code into it:

body {
  margin: 0;
  background-color: #814BF6;
}

.header {
  background-color: #110E27;
  padding: 15px;
  margin: 0; /* Spacing around the content */
  text-align: center; /* Center content horizontally */
}

.header-logo {
  display: inline-block; /* Change block element to inline-block */
  width: 500px;
  height: auto; /* Automatic height adjustment */
}

.header-title {
  font-size: 24px;
  color: #FFFFFF;
  margin-top: 10px;
}

.main {
  display: flex;
  justify-content: center; 
}

Next, we'll edit the index.html file, where the structure of the webpage is defined. You can copy the following content into it:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>PDF Merge Tool</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="assets/favicon.png">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
</head>
<body class="body">
	<header class="header">
  		<h1 class="header-title">PDF Merge Tool</h1>
	</header>
	<main class="main">
		<app-root>
			<pdf></pdf>
		</app-root>
	</main>
</body>
</html>

Tip: If you're new to programming and don't understand everything, you can copy the code into a LLM like ChatGPT and ask the model to explain the code to you. This way, you can learn a lot while working on your project.

For now, let's skip the assets folder. You can use this folder to store images or other assets to be used on your webpage.

Now, you can open the app folder. It already contains some files. In the app folder, you'll find all the components you create or import during your project. In Angular, you can build components that you can combine and reuse as needed.

In the app.module.ts file, various dependencies and used components need to be specified to make the components work correctly. For starters, you can copy the following code and replace it with the code in your app.module.ts file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { PdfComponent } from './pdf/pdf.component';

@NgModule({
  declarations: [
    AppComponent,
    PdfComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

This code imports necessary modules and declares components. You will need to adjust it to match the components and modules in your project.

In the appmodule.html file, delete the existing code and copy the following:

<pdf></pdf>

You might be wondering what this is for. We will now create a new component and configure it so that this component can be called using this HTML tag.

In the terminal, you can easily paste the following command. Make sure you are in the project folder in the terminal:

ng g c pdf-upload

This command creates a completely new component that will be placed in your app folder as the pdf-upload folder. This folder, in turn, contains multiple files that we can edit.

Now, let's write the logic for the component using TypeScript. To do this, we will edit the pdf-upload.component.ts file:

import { Component } from '@angular/core';
import { PDFDocument } from 'pdf-lib';
import { FormsModule } from '@angular/forms';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';



@Component({
  selector: 'pdf',
  templateUrl: './pdf-upload.component.html',
  styleUrls: ['./pdf-upload.component.css'],
})
export class AppPdfUploadComponent {
  uploadedPdfs: File[] = [];
  mergedPdf: Uint8Array | null = null;
  // In Ihrer Angular-Komponente (z. B. app-pdf-upload.component.ts)
  newFileName: string = 'neuer_dateiname.pdf'; // Standardwert für den Dateinamen

  /**
 * Coordinates the merging of multiple PDFs into a single PDF.
 * Reads and processes the uploaded PDF files and stores the merged PDF.
 */
  async coordinateMergePDFs() {
    const pdfs = await Promise.all(this.uploadedPdfs.map(file => this.readPDF(file)));
    this.mergedPdf = await this.mergePDFsIntoOne(pdfs);
  }


  async readPDF(file: File): Promise<Uint8Array> {
    return new Promise<Uint8Array>((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = (event) => {
        if (event.target?.result) {
          resolve(new Uint8Array(event.target.result as ArrayBuffer));
        } else {
          reject('Could not read the PDF file');
        }
      };
      reader.readAsArrayBuffer(file);
    });
  }

  /** this function is merging PDFs and return the PDFs data as a Unit8Array*/
  async mergePDFsIntoOne(pdfs: Uint8Array[]): Promise<Uint8Array> {
    const mergedPdf = await PDFDocument.create();
    for (const pdfBytes of pdfs) {
      const pdf = await PDFDocument.load(pdfBytes);
      const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
      copiedPages.forEach((page) => {
        mergedPdf.addPage(page);
      });
    }
    return await mergedPdf.save();
  }

  reorderFiles(event: any) {
    const files: FileList = event.target.files;
    for (let i = 0; i < files.length; i++) {
      this.uploadedPdfs.push(files[i]);
    }
    this.coordinateMergePDFs();
  }

downloadMergedPDF() {
  if (!this.mergedPdf) {
    return;
  }
  const blob = new Blob([this.mergedPdf], { type: 'application/pdf' });
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  document.body.appendChild(a);
  a.style.display = 'none';
  a.href = url;

  // Verwenden Sie den Wert aus dem Textfeld als Dateinamen
  a.download = this.newFileName; // Hier wird der Dateiname aus dem Textfeld verwendet

  a.click();
  window.URL.revokeObjectURL(url);
  document.body.removeChild(a);
  }

   drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.uploadedPdfs, event.previousIndex, event.currentIndex);
	this.coordinateMergePDFs();
  }

}

This code defines the behavior and functionality of your PDF merging component. It allows you to upload PDF files, merge them, and download the merged PDF with a specified filename. Additionally, it supports drag-and-drop reordering of uploaded PDFs.

Now, let's edit the CSS file of the component to define its style. You can add the following styles to set the appearance of the component:

.pdf-upload-zone {
  border: 2px dashed #ccc;
  padding: 20px;
  text-align: center;
}

.drop-zone-label {
  font-size: 18px;
}

.upload-controls {
  margin-top: 20px;
  text-align: center;
}

.merged-pdf {
  margin-top: 20px;
}

.pdf-upload-zone {
  /* ... */
  transition: border 0.3s ease-in-out; /* Example of a transition animation */
}

.cdk-drop-list {
  border: 2px dashed #ccc;
  padding: 20px;
  text-align: center;
}

.example-list {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 500px;
  max-width: 100%;
  border: none;
  min-height: 60px;
  display: block;
  background: #814BF6;
  border-radius: 4px;
  overflow: hidden;
  font-family: Inter, sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: white;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: #110E27;
  font-size: 20px;
  text-align: center;
  font-family: Inter, sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

These CSS styles define the appearance of various elements within your PDF merging component, including the upload zone, labels, controls, and the drag-and-drop list. You can customize these styles further to match your project's design requirements.

Finally, let's define the structure in the HTML code for the component:

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
   <div class="example-box" *ngFor="let pdf of uploadedPdfs" cdkDrag>{{pdf.name}}</div>
</div>

<div align-items="center" class="upload-controls">
    <input type="file" (change)="reorderFiles($event)" multiple accept=".pdf">
</div>

<div *ngIf="mergedPdf" class="merged-pdf">
    <h2>Merged PDF</h2>
    <input type="text" [(ngModel)]="newFileName" placeholder="New Filename"> 
    <button (click)="downloadMergedPDF()">Download Merged PDF</button>
</div>

Okay, we have now completed writing our PDF Merge Tool. Now the Merge Tool should be visible in the browser. If the ng serve command was stopped in the meantime, you can easily run it again in the terminal:

ng serve --port 3000 --host 0.0.0.0 --public-host XXXXX-3000.2.codesphere.com

Now, when you click the "Deploy" button, you will be redirected to your online PDF Merge Tool in the browser. Congratulations, your first Angular app is now online!

However, it's important to note that the ng serve command should only be used for testing, as it may have security vulnerabilities.

To deploy a finished web app, you should use the following command:

ng build --port 3000 --host 0.0.0.0 --public-host XXXXX-3000.2.codesphere.com

In this tutorial, we have covered the basic functions of Angular and learned how to start, edit, and deploy Angular projects in Codesphere. Angular is a versatile framework, and we have only scratched the surface. To learn more about Angular, I recommend watching YouTube videos, using ChatGPT or other language models, and most importantly, actively experimenting on your own.

If you need help with Angular on Codesphere, feel free to contact us on our Codesphere Community Discord server: https://discord.gg/codesphere