Angular Drag and Drop Files Upload Made Simple

tabernerojerry

Jerry Tabernero

Posted on April 15, 2024

Angular Drag and Drop Files Upload Made Simple

NO drag and drop dependency and directives needed. Simple just input file that's it!

Setting Up Angular Project

To create a new Angular project, you can use the Angular command-line tool.

Open your terminal and execute the following command:



npx -p @angular/cli ng new simple-dnd-upload-files


Enter fullscreen mode Exit fullscreen mode

Follow the instructions and once the project is created, navigate to the project directory.



cd simple-dnd-upload-files


Enter fullscreen mode Exit fullscreen mode

Install Tailwind CSS

Install tailwind via npm, and then run the init command to generate a tailwind.config.js file.



npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init


Enter fullscreen mode Exit fullscreen mode

Add the paths to all of your template files in your tailwindcss.config.js file.



/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{html,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}


Enter fullscreen mode Exit fullscreen mode

Add the @tailwind directives for each of Tailwind’s layers to your ./src/styles.css file.



@tailwind base;
@tailwind components;
@tailwind utilities;


Enter fullscreen mode Exit fullscreen mode

Run the Application

Run your build process with:



ng serve


Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:4200 to view the default angular page.

Image description

Update HTML Component

For simplicity, we will use the app.component.html file to define the HTML structure of the drop zone.



<div class="flex flex-col items-center px-6 pt-[100px] bg-gray-50 h-lvh w-lvw">
  <h1 class="leading-none text-4xl font-bold mb-[50px]">Drag and Drop Files Upload</h1>

  <div class="flex items-center content-center min-h-[350px] gap-x-6">
    <!-- Start Drop Zone -->
    <div
      class="relative flex flex-col outline-gray-500 outline-2 -outline-offset-4 outline-dashed w-[350px] h-[350px] rounded-2xl items-center justify-center"
    >
      <input
        #fileInput
        type="file"
        (change)="handleChange($event)"
        [accept]="allowedFileTypes"
        class="absolute w-full h-full cursor-pointer opacity-0 top-0 left-0"
      />

      <span class="mb-6">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-10 h-10">
          <path stroke-linecap="round" stroke-linejoin="round" d="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15" />
        </svg>
      </span>

      <p class="text-center text-base max-w-[90%]">
        Drag and drop <br />
        file here to upload. <br />
        (PNG, JPG, SVG)
      </p>

      <button
        type="button"
        (click)="fileInput.click()"
        class="z-10 mt-6 text-green-50 px-6 py-3 bg-green-500 rounded-lg font-medium text-base hover:bg-green-600 ease-in-out"
      >
        Browse File
      </button>
    </div>
    <!-- End Drop Zone -->

    <!-- Start Preview Image -->
    <div
      *ngIf="fileUrl && uploadFile"
      [ngStyle]="{ 'background-image': 'url(' + fileUrl + ')' }"
      class="flex flex-col justify-end border w-[350px] h-[350px] rounded-2xl items-center relative bg-cover bg-center overflow-hidden bg-gray-300"
    >
      <div
        class="flex flex-col w-full p-4 bg-white"
      >
        <p class="mb-6">{{ uploadFile?.name }}</p>

        <div class="flex gap-3 justify-start">
          <button
            type="button"
            [disabled]="isUploading"
            (click)="handleUploadFile()"
            class="text-sm font-medium w-[50%] border border-blue-500 px-4 py-3 bg-blue-500 text-blue-50 rounded-lg hover:bg-blue-600 ease-in-out disabled:bg-gray-300 disabled:border-gray-300"
          >
            {{ !isUploading ? 'UPLOAD' : 'UPLOADING...'}}
          </button>
          <button
          type="button"
          [disabled]="isUploading"
          (click)="handleRemovesFile()"
          class="text-sm font-medium w-[50%] border border-red-500 px-4 py-3 rounded-lg text-red-500 hover:bg-red-500 hover:text-white ease-in-out"
          >
            REMOVE
          </button>
        </div>
      </div>
    </div>
    <!-- End Preview Image -->
  </div>
</div>


Enter fullscreen mode Exit fullscreen mode
  • We have set the input file to take its parent container space and assign opacity to zero.
  • We define a browse file button in which the z-index has a greater value than the input file.
  • And define the preview image container to display the image that the user going to upload.

Image description

Drop Zone Component Logic

We gonna use the app.component.ts file to declare variables and methods to handle the drag-and-drop logic.



const ALLOWED_FILE_TYPES = [
  'image/jpeg',
  'image/png',
  'image/svg+xml',
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  @ViewChild('fileInput', { static: false }) fileInput!: ElementRef;

  allowedFileTypes = ALLOWED_FILE_TYPES;

  isUploading = false;
  fileUrl!: string | null;
  uploadFile!: File | null;

  handleChange(event: any) {
    const file = event.target.files[0] as File;
    this.fileUrl = URL.createObjectURL(file);
    this.uploadFile = file;
  }

  handleRemovesFile() {
    if (this.fileInput && this.fileInput.nativeElement) {
      this.fileInput.nativeElement.value = null;
    }

    this.uploadFile = null;
    this.fileUrl = null;
  }

  handleUploadFile() {
    // logic to upload file
  }
}


Enter fullscreen mode Exit fullscreen mode
  • The handle change method used to watch every file changes and update the fileUrl and uploadFile values.
  • The handle removes file method is used to reset the input file, uploadFile, and fileUrl values.

Image description

Allowed Files Validation

For simplicity, we only allowed (JPG, PNG, and SVG) files to be uploaded. And show the error message using the ngxpert/hot-toast library.

Install the hot-toast library via npm:



npm install @ngneat/overview@6.0.0 @ngxpert/hot-toast


Enter fullscreen mode Exit fullscreen mode

Add the hot-toast styles inside angular.json file.



"styles": [
  "src/styles.css",
  "node_modules/@ngxpert/hot-toast/src/styles/styles.css"
 ],


Enter fullscreen mode Exit fullscreen mode

Add provideHotToastConfig() to your app.config.ts providers section.



export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes), 
    provideHotToastConfig({
      dismissible: true,
      duration: 5000,
    })
  ]
};


Enter fullscreen mode Exit fullscreen mode

Inject the HotToastService in the app.component.ts file.



#hotToastService = inject(HotToastService);


Enter fullscreen mode Exit fullscreen mode

Update the handle change method with the changes below:



handleChange(event: any) {
    const file = event.target.files[0] as File;

    if (this.allowedFileTypes.indexOf(file?.type) === -1) {
      this.#hotToastService.error('File type is not allowed.');
      this.handleRemovesFile();
      return;
    }

    this.fileUrl = URL.createObjectURL(file);
    this.uploadFile = file;
  }


Enter fullscreen mode Exit fullscreen mode
  • We check the file type if it exists in the allowed file types array if not we will show the error message and immediately remove the attached file then cancel the process.

Image description

Handle Upload File

We will use the object-to-formdata library to easily serializes Objects to FormData instances.

Install the object-to-formdata library via npm:



npm install object-to-formdata


Enter fullscreen mode Exit fullscreen mode

Update the handle upload file method with the changes below:



handleUploadFile() {
    this.isUploading = true;

    const formData = serialize({
      document: this.uploadFile
    });

    // your API service logic to upload file
  }


Enter fullscreen mode Exit fullscreen mode
  • I will leave to you the API service logic to upload the file and the uploading state logic to update the value.

Image description

That's it!😎

Summary

You can find the full project on GitHub.

Did you learn something new? If so please:

👉 clap 👏 button multiple times so more people can see this.

Keep in Mind

This approach will work with other JavaScript libraries like React, SolidJS, Svelte, and even plain JavaScript.

💖 💪 🙅 🚩
tabernerojerry
Jerry Tabernero

Posted on April 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related