0%

angular文件上传,支持多文件、拖拽、重试以及上传进度

file-upload

1. Dependencies

  • @angular/cdk
  • @angular/material

2. file-upload组件

  • file-upload.component.html

    1
    <div class="upload-container"
    2
         #uploadzone
    3
         dropzone
    4
         [class.hovering]="uploadzone.isHovering"
    5
         (hovered)="uploadzone.isHovering = $event"
    6
         (dropped)="onDrop($event)"
    7
         (click)="onClick()">
    8
      <p class="icon"><mat-icon>cloud_upload</mat-icon></p>
    9
      <p class="title">Click or drag file to this area to upload</p>
    10
      <p class="desc">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
    11
    </div>
    12
    13
    <ul>
    14
        <li *ngFor="let file of files" [@fadeInOut]="file.state">
    15
          <mat-progress-bar [value]="file.progress"></mat-progress-bar>
    16
          <span class="file-label">
    17
          {{file.data.name}} 
    18
          <a title="Retry" (click)="retryFile(file)" *ngIf="file.canRetry">
    19
          <mat-icon>refresh</mat-icon></a>
    20
          <a title="Cancel" (click)="cancelFile(file)" *ngIf="file.canCancel">
    21
          <mat-icon>cancel</mat-icon></a>
    22
          </span>
    23
      </li>
    24
    </ul>
    25
    26
    <input
    27
      type="file"
    28
      id="fileUpload"
    29
      name="fileUpload"
    30
      multiple="multiple"
    31
      accept="{{accept}}"
    32
      style="display:none;"/>
  • file-upload.component.scss

1
@import '~@angular/material/theming';
2
3
.upload-container {
4
  display: flex;
5
  flex-direction: column;
6
  align-content: center;
7
  justify-content: center;
8
  text-align: center;
9
  background: #fafafa;
10
  border: 1px dashed #d9d9d9;
11
  padding: 16px 0;
12
  margin: 20px;
13
  cursor: pointer;
14
  p {
15
    margin: 0;
16
    padding: 0;
17
  }
18
  .icon {
19
    margin-bottom: 20px;
20
  }
21
  .title {
22
    margin: 0 0 4px;
23
    color: rgba(0,0,0,.85);
24
    font-size: 16px;
25
  }
26
  .desc {
27
    color: rgba(0,0,0,.45);
28
    font-size: 14px;
29
  }
30
}
31
32
.hovering {
33
  border: 1px dashed tomato;
34
}
35
36
ul,
37
li {
38
    margin: 0 20px;
39
    padding: 0;
40
    list-style: none;
41
    mat-progress-bar {
42
        border-radius: 10px;
43
    }
44
    .file-label {
45
        display: inline-flex;
46
        vertical-align: middle;
47
        font-size: 12px;
48
        line-height: 18px;
49
        justify-content: flex-end;
50
        width: 100%;
51
        padding-top: 5px;
52
        mat-icon {
53
            font-size: 18px;
54
            text-align: center;
55
        }
56
        mat-icon:hover {
57
            color: map-get($mat-red, 500);
58
        }
59
        a {
60
            margin-left: 4px;
61
            cursor: pointer;
62
        }
63
    }
64
}
  • file-upload.component.ts
1
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
2
import {
3
  trigger,
4
  state,
5
  style,
6
  animate,
7
  transition
8
} from "@angular/animations";
9
import {
10
  HttpClient,
11
  HttpResponse,
12
  HttpRequest,
13
  HttpEventType,
14
  HttpErrorResponse
15
} from "@angular/common/http";
16
import { of } from "rxjs";
17
import { catchError, last, map, tap } from "rxjs/operators";
18
import { FileUploadModel } from './file-upload.model';
19
20
@Component({
21
  selector: "app-file-upload",
22
  templateUrl: "./file-upload.component.html",
23
  styleUrls: ["./file-upload.component.scss"],
24
  animations: [
25
    trigger("fadeInOut", [
26
      state("in", style({ opacity: 100 })),
27
      transition("* => void", [animate(300, style({ opacity: 0 }))])
28
    ])
29
  ]
30
})
31
export class FileUploadComponent implements OnInit {
32
  /** Link text */
33
  @Input() text = "Upload";
34
  /** Name used in form which will be sent in HTTP request. */
35
  @Input() param = "file";
36
  /** Target URL for file uploading. */
37
  @Input() target = "https://file.io";
38
  /** File extension that accepted, same as 'accept' of <input type="file" />. By the default, it's set to 'image/*'. */
39
  @Input() accept = "image/*";
40
  /** Allow you to add handler after its completion. Bubble up response text from remote. */
41
  @Output() complete = new EventEmitter<string>();
42
43
  private files: Array<FileUploadModel> = [];
44
45
  constructor(private _http: HttpClient) {}
46
47
  ngOnInit() {}
48
49
  onDrop(files: FileList) {
50
    for (let index = 0; index < files.length; index++) {
51
      const file = files[index];
52
      this.files.push({
53
        data: file,
54
        state: "in",
55
        inProgress: false,
56
        progress: 0,
57
        canRetry: false,
58
        canCancel: true
59
      });
60
      this.uploadFiles();
61
    }
62
  }
63
64
  onClick() {
65
    const fileUpload = document.getElementById(
66
      "fileUpload"
67
    ) as HTMLInputElement;
68
    fileUpload.onchange = () => {
69
      for (let index = 0; index < fileUpload.files.length; index++) {
70
        const file = fileUpload.files[index];
71
        this.files.push({
72
          data: file,
73
          state: "in",
74
          inProgress: false,
75
          progress: 0,
76
          canRetry: false,
77
          canCancel: true
78
        });
79
      }
80
      this.uploadFiles();
81
    };
82
    fileUpload.click();
83
  }
84
85
  cancelFile(file: FileUploadModel) {
86
    file.sub.unsubscribe();
87
    this.removeFileFromArray(file);
88
  }
89
90
  retryFile(file: FileUploadModel) {
91
    this.uploadFile(file);
92
    file.canRetry = false;
93
  }
94
95
  private uploadFile(file: FileUploadModel) {
96
    const fd = new FormData();
97
    fd.append(this.param, file.data);
98
99
    const req = new HttpRequest("POST", this.target, fd, {
100
      reportProgress: true
101
    });
102
103
    file.inProgress = true;
104
    file.sub = this._http
105
      .request(req)
106
      .pipe(
107
        map(event => {
108
          switch (event.type) {
109
            case HttpEventType.UploadProgress:
110
              file.progress = Math.round((event.loaded * 100) / event.total);
111
              break;
112
            case HttpEventType.Response:
113
              return event;
114
          }
115
        }),
116
        tap(message => {}),
117
        last(),
118
        catchError((error: HttpErrorResponse) => {
119
          file.inProgress = false;
120
          file.canRetry = true;
121
          return of(`${file.data.name} upload failed.`);
122
        })
123
      )
124
      .subscribe((event: any) => {
125
        if (typeof event === "object") {
126
          this.removeFileFromArray(file);
127
          this.complete.emit(event.body);
128
        }
129
      });
130
  }
131
132
  private uploadFiles() {
133
    const fileUpload = document.getElementById(
134
      "fileUpload"
135
    ) as HTMLInputElement;
136
    fileUpload.value = "";
137
138
    this.files.forEach(file => {
139
      this.uploadFile(file);
140
    });
141
  }
142
143
  private removeFileFromArray(file: FileUploadModel) {
144
    const index = this.files.indexOf(file);
145
    if (index > -1) {
146
      this.files.splice(index, 1);
147
    }
148
  }
149
150
}
  • file-upload.model.ts
1
import { Subscription } from "rxjs";
2
3
export class FileUploadModel {
4
  data: File;
5
  state: string;
6
  inProgress: boolean;
7
  progress: number;
8
  canRetry: boolean;
9
  canCancel: boolean;
10
  sub?: Subscription;
11
}

3. drop指令

  • dropzone.directive.ts
1
import { Directive, HostListener, Output, EventEmitter } from "@angular/core";
2
3
@Directive({
4
  selector: "[dropzone]"
5
})
6
export class DropzoneDirective {
7
  @Output() dropped = new EventEmitter<FileList>();
8
  @Output() hovered = new EventEmitter<boolean>();
9
10
  @HostListener("drop", ["$event"])
11
  onDrop($event) {
12
    $event.preventDefault();
13
    this.dropped.emit($event.dataTransfer.files);
14
    this.hovered.emit(false);
15
  }
16
17
  @HostListener("dragover", ["$event"])
18
  onDragOver($event) {
19
    $event.preventDefault();
20
    this.hovered.emit(true);
21
  }
22
23
  @HostListener("dragleave", ["$event"])
24
  onDragLeave($event) {
25
    $event.preventDefault();
26
    this.hovered.emit(false);
27
  }
28
}

4. Demo Stackblitz 地址

angular-drag-file-upload