Exemplo de Implementação:
import { Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
import { signal, computed } from '@angular/core';
@Component({
selector: 'app-file-upload',
imports: [NgOptimizedImage],
template: `
<div class="upload-container" [class.dragging]="isDragging()">
<h2>Upload File</h2>
<ng-content></ng-content>
<div class="drop-zone"
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
(drop)="onFileDrop($event)">
<ng-optimized-image src="/assets/upload-icon.png"
width="64"
height="64"
alt="Upload icon"
class="upload-icon">
</ng-optimized-image>
<p>Arraste e solte um arquivo aqui ou clique para selecionar</p>
<input type="file"
(change)="onFileSelected($event)"
class="file-input"
aria-label="Selecionar arquivo para upload">
</div>
@if (isUploading()) {
<div class="upload-progress">
<progress [value]="progress()" max="100"></progress>
<span>{{ progress() }}%</span>
</div>
}
@if (errorMessage()) {
<div class="error-message" role="alert">
{{ errorMessage() }}
</div>
}
@if (successMessage()) {
<div class="success-message" role="status">
{{ successMessage() }}
</div>
}
</div>
`,
styles: [`
.upload-container {
padding: 2rem;
border: 2px dashed #ccc;
border-radius: 8px;
text-align: center;
transition: all 0.2s ease;
}
.upload-container.dragging {
border-color: #007bff;
background-color: #f8f9ff;
}
.drop-zone {
cursor: pointer;
padding: 3rem;
}
.file-input {
display: none;
}
.upload-progress {
margin-top: 1.5rem;
}
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 1rem;
border-radius: 4px;
margin-top: 1rem;
}
.success-message {
background-color: #d4edda;
color: #155724;
padding: 1rem;
border-radius: 4px;
margin-top: 1rem;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'file-upload-component',
'[attr.aria-label]': "'File upload component'",
'(keydown)': 'handleKeyDown($event)'
}
})
export class FileUploadComponent {
readonly acceptedTypes = input<string[]>(['.pdf', '.jpg', '.png']);
readonly maxSizeMB = input<number>(10);
readonly uploadComplete = output<File>();
readonly uploadFailed = output<{file: File; error: string}>();
private readonly isDragging = signal(false);
private readonly isUploading = signal(false);
private readonly progress = signal(0);
private readonly errorMessage = signal<string | null>(null);
private readonly successMessage = signal<string | null>(null);
readonly uploadStatus = computed(() => {
if (this.isUploading()) {
return 'uploading';
}
if (this.errorMessage()) {
return 'error';
}
if (this.successMessage()) {
return 'success';
}
return 'idle';
});
constructor(private fileService: FileService) {}
onDragOver(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.isDragging.set(true);
}
onDragLeave(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.isDragging.set(false);
}
async onFileDrop(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.isDragging.set(false);
const files = event.dataTransfer?.files;
if (files && files.length > 0) {
await this.handleFileUpload(files[0]);
}
}
async onFileSelected(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
await this.handleFileUpload(input.files[0]);
input.value = '';
}
}
private async handleFileUpload(file: File): Promise<void> {
const fileExt = `.${file.name.split('.').pop().toLowerCase()}`;
if (!this.acceptedTypes().includes(fileExt)) {
this.uploadFailed.emit({ file, error: `Tipo de arquivo não permitido. Tipos aceitos: ${this.acceptedTypes().join(', ')}` });
return;
}
const maxSizeBytes = this.maxSizeMB() * 1024 * 1024;
if (file.size > maxSizeBytes) {
this.uploadFailed.emit({ file, error: `Arquivo muito grande. Tamanho máximo: ${this.maxSizeMB()}MB` });
return;
}
this.isUploading.set(true);
this.progress.set(0);
this.errorMessage.set(null);
this.successMessage.set(null);
try {
for (let i = 0; i <= 100; i += 10) {
await new Promise(resolve => setTimeout(resolve, 50));
this.progress.set(i);
}
await this.fileService.uploadFile(file);
this.successMessage.set(`Arquivo "${file.name}" enviado com sucesso!`);
this.progress.set(100);
this.uploadComplete.emit(file);
} catch (error) {
this.errorMessage.set(`Erro ao enviar arquivo: ${error.message}`);
this.uploadFailed.emit({ file, error: error.message });
} finally {
setTimeout(() => {
this.isUploading.set(false);
this.progress.set(0);
this.errorMessage.set(null);
this.successMessage.set(null);
}, 3000);
}
}
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const fileInput = document.querySelector('.file-input') as HTMLInputElement;
fileInput?.click();
}
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { firstValueFrom } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class FileService {
private http = inject(HttpClient);
private apiUrl = '/api/files';
async uploadFile(file: File): Promise<void> {
const formData = new FormData();
formData.append('file', file);
try {
await firstValueFrom(this.http.post(`${this.apiUrl}/upload`, formData));
} catch (error) {
throw error;
}
}
}
import { Routes } from '@angular/router';
import { FileUploadComponent } from './components/file-upload/file-upload.component';
import { FileListComponent } from './components/file-list/file-list.component';
export const routes: Routes = [
{
path: '',
redirectTo: '/files',
pathMatch: 'full'
},
{
path: 'files',
loadComponent: () => import('./components/file-list/file-list.component')
.then(m => m.FileListComponent),
title: 'File List'
},
{
path: 'upload',
component: FileUploadComponent,
title: 'Upload File'
}
];
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient()
]
};