1. Dependencies
- @angular/cdk
- @angular/material
2. file-upload组件
- file-upload.component.html
<div class="upload-container"
#uploadzone
dropzone
[class.hovering]="uploadzone.isHovering"
(hovered)="uploadzone.isHovering = $event"
(dropped)="onDrop($event)"
(click)="onClick()">
<p class="icon"><mat-icon>cloud_upload</mat-icon></p>
<p class="title">Click or drag file to this area to upload</p>
<p class="desc">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</div>
<ul>
<li *ngFor="let file of files" [@fadeInOut]="file.state">
<mat-progress-bar [value]="file.progress"></mat-progress-bar>
<span class="file-label">
{{file.data.name}}
<a title="Retry" (click)="retryFile(file)" *ngIf="file.canRetry">
<mat-icon>refresh</mat-icon></a>
<a title="Cancel" (click)="cancelFile(file)" *ngIf="file.canCancel">
<mat-icon>cancel</mat-icon></a>
</span>
</li>
</ul>
<input
type="file"
id="fileUpload"
name="fileUpload"
multiple="multiple"
accept="{{accept}}"
style="display:none;"/>
- file-upload.component.scss
@import '~@angular/material/theming';
.upload-container {
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
text-align: center;
background: #fafafa;
border: 1px dashed #d9d9d9;
padding: 16px 0;
margin: 20px;
cursor: pointer;
p {
margin: 0;
padding: 0;
}
.icon {
margin-bottom: 20px;
}
.title {
margin: 0 0 4px;
color: rgba(0,0,0,.85);
font-size: 16px;
}
.desc {
color: rgba(0,0,0,.45);
font-size: 14px;
}
}
.hovering {
border: 1px dashed tomato;
}
ul,
li {
margin: 0 20px;
padding: 0;
list-style: none;
mat-progress-bar {
border-radius: 10px;
}
.file-label {
display: inline-flex;
vertical-align: middle;
font-size: 12px;
line-height: 18px;
justify-content: flex-end;
width: 100%;
padding-top: 5px;
mat-icon {
font-size: 18px;
text-align: center;
}
mat-icon:hover {
color: map-get($mat-red, 500);
}
a {
margin-left: 4px;
cursor: pointer;
}
}
}
- file-upload.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
import {
trigger,
state,
style,
animate,
transition
} from "@angular/animations";
import {
HttpClient,
HttpResponse,
HttpRequest,
HttpEventType,
HttpErrorResponse
} from "@angular/common/http";
import { of } from "rxjs";
import { catchError, last, map, tap } from "rxjs/operators";
import { FileUploadModel } from './file-upload.model';
@Component({
selector: "app-file-upload",
templateUrl: "./file-upload.component.html",
styleUrls: ["./file-upload.component.scss"],
animations: [
trigger("fadeInOut", [
state("in", style({ opacity: 100 })),
transition("* => void", [animate(300, style({ opacity: 0 }))])
])
]
})
export class FileUploadComponent implements OnInit {
/** Link text */
@Input() text = "Upload";
/** Name used in form which will be sent in HTTP request. */
@Input() param = "file";
/** Target URL for file uploading. */
@Input() target = "https://file.io";
/** File extension that accepted, same as 'accept' of <input type="file" />. By the default, it's set to 'image/*'. */
@Input() accept = "image/*";
/** Allow you to add handler after its completion. Bubble up response text from remote. */
@Output() complete = new EventEmitter<string>();
private files: Array<FileUploadModel> = [];
constructor(private _http: HttpClient) {}
ngOnInit() {}
onDrop(files: FileList) {
for (let index = 0; index < files.length; index++) {
const file = files[index];
this.files.push({
data: file,
state: "in",
inProgress: false,
progress: 0,
canRetry: false,
canCancel: true
});
this.uploadFiles();
}
}
onClick() {
const fileUpload = document.getElementById(
"fileUpload"
) as HTMLInputElement;
fileUpload.onchange = () => {
for (let index = 0; index < fileUpload.files.length; index++) {
const file = fileUpload.files[index];
this.files.push({
data: file,
state: "in",
inProgress: false,
progress: 0,
canRetry: false,
canCancel: true
});
}
this.uploadFiles();
};
fileUpload.click();
}
cancelFile(file: FileUploadModel) {
file.sub.unsubscribe();
this.removeFileFromArray(file);
}
retryFile(file: FileUploadModel) {
this.uploadFile(file);
file.canRetry = false;
}
private uploadFile(file: FileUploadModel) {
const fd = new FormData();
fd.append(this.param, file.data);
const req = new HttpRequest("POST", this.target, fd, {
reportProgress: true
});
file.inProgress = true;
file.sub = this._http
.request(req)
.pipe(
map(event => {
switch (event.type) {
case HttpEventType.UploadProgress:
file.progress = Math.round((event.loaded * 100) / event.total);
break;
case HttpEventType.Response:
return event;
}
}),
tap(message => {}),
last(),
catchError((error: HttpErrorResponse) => {
file.inProgress = false;
file.canRetry = true;
return of(`${file.data.name} upload failed.`);
})
)
.subscribe((event: any) => {
if (typeof event === "object") {
this.removeFileFromArray(file);
this.complete.emit(event.body);
}
});
}
private uploadFiles() {
const fileUpload = document.getElementById(
"fileUpload"
) as HTMLInputElement;
fileUpload.value = "";
this.files.forEach(file => {
this.uploadFile(file);
});
}
private removeFileFromArray(file: FileUploadModel) {
const index = this.files.indexOf(file);
if (index > -1) {
this.files.splice(index, 1);
}
}
}
- file-upload.model.ts
import { Subscription } from "rxjs";
export class FileUploadModel {
data: File;
state: string;
inProgress: boolean;
progress: number;
canRetry: boolean;
canCancel: boolean;
sub?: Subscription;
}
3. drop指令
- dropzone.directive.ts
import { Directive, HostListener, Output, EventEmitter } from "@angular/core";
@Directive({
selector: "[dropzone]"
})
export class DropzoneDirective {
@Output() dropped = new EventEmitter<FileList>();
@Output() hovered = new EventEmitter<boolean>();
@HostListener("drop", ["$event"])
onDrop($event) {
$event.preventDefault();
this.dropped.emit($event.dataTransfer.files);
this.hovered.emit(false);
}
@HostListener("dragover", ["$event"])
onDragOver($event) {
$event.preventDefault();
this.hovered.emit(true);
}
@HostListener("dragleave", ["$event"])
onDragLeave($event) {
$event.preventDefault();
this.hovered.emit(false);
}
}
4. Demo Stackblitz 地址
声明:文章参考进行改进:https://stackblitz.com/edit/angular-material-file-upload-jtcn1z