Jerry Tabernero
Posted on April 15, 2024
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.
/** @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.
@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.
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>
- 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.
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
}
}
- The handle change method used to watch every file changes and update the
fileUrl
anduploadFile
values. - The handle removes file method is used to reset the
input file
,uploadFile
, andfileUrl
values.
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.
"styles": [
"src/styles.css",
"node_modules/@ngxpert/hot-toast/src/styles/styles.css"
],
Add provideHotToastConfig()
to your app.config.ts
providers section.
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHotToastConfig({
dismissible: true,
duration: 5000,
})
]
};
Inject the HotToastService
in the app.component.ts
file.
#hotToastService = inject(HotToastService);
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;
}
- 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.
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:
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.
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.
Posted on April 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.