Angular Drag and Drop Files Upload Made Simple
Tags: angular, frontend
Published: 2024-04-15 16:23:13

**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
```

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

```
cd simple-dnd-upload-files
```

## 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
```

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

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

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

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Run the Application
Run your build process with:

```
ng serve
```

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6orziwxuok5x454t0ii.jpg)

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

```html
<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>
```

- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzx96omxzg324aqtr1ge.jpg)

## Drop Zone Component Logic
We gonna use the `app.component.ts` file to declare variables and methods to handle the drag-and-drop logic.

```typescript
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
  }
}
```
- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rftomml6t1ieu13yw6ps.jpg)

## 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
```

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

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

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

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

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

```typescript
#hotToastService = inject(HotToastService);
```

Update the handle change method with the changes below:

```typescript
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;
  }
```
- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7utqbpeqib4ud8wwod3j.jpg)

## 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
```

Update the handle upload file method with the changes below:

```typescript
handleUploadFile() {
    this.isUploading = true;

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

    // your API service logic to upload file
  }
```
- I will leave to you the API service logic to upload the file and the uploading state logic to update the value.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95wb4dnaakly7qnb3ym9.jpg)

That's it!😎

## Summary
You can find the full project on [GitHub](https://github.com/tabernerojerry/ng-simple-dnd-upload-files).

## 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.