使用 Angular2 将文件上传到 REST API
File Upload with Angular2 to REST API
实际上,我正在开发 Spring REST API,其接口编码为 Angular 2.
我的问题是我无法使用 Angular 2.
上传文件
我在 java 中的网络资源是:
@RequestMapping(method = RequestMethod.POST, value = "/upload")
public String handleFileUpload(@RequestParam MultipartFile file) {
//Dosomething
}
当我通过 URL 请求使用 Auth header 等调用它时,它工作正常......
(带有 Chrome 的 Advanced Rest Client 扩展)
证明:(在那种情况下一切正常)
我添加了
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
Spring 配置文件和 Pom 依赖项
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
但是当我尝试用网络表单做同样的事情时:
<input type="file" #files (change)="change(files)"/>
<pre>{{fileContents$|async}}</pre>
使用(更改)方法:
change(file) {
let formData = new FormData();
formData.append("file", file);
console.log(formData);
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
'Content-Type': 'multipart/form-data'
});
this.http.post(this.url, formData, {headers}).map(res => res.json()).subscribe((data) => console.log(data));
/*
Observable.fromPromise(fetch(this.url,
{method: 'post', body: formData},
{headers: this.headers}
)).subscribe(()=>console.log('done'));
*/
}
我的网络服务 return 给我一个错误 500,在 tomcat 日志中:http://pastebin.com/PGdcFUQb
我也尝试了 'Content-Type': undefined
方法但没有成功(网络服务 return 我在这种情况下出现 415 错误。
谁能帮我弄清楚问题出在哪里?
问题已解决,稍后我会用我的代码更新该问题 :) 但是,看看 plunker,它运行良好。
谢谢。
事实上,目前您只能为 Angular2 HTTP 支持的 post
、put
和 patch
方法提供字符串输入。
为此,您需要直接利用 XHR 对象,如下所述:
import {Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Rx';
@Injectable()
export class UploadService {
constructor () {
this.progress$ = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
private makeFileRequest (url: string, params: string[], files: File[]): Observable {
return Observable.create(observer => {
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
for (let i = 0; i < files.length; i++) {
formData.append("uploads[]", files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.upload.onprogress = (event) => {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
}
有关详细信息,请参阅此插件:https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info。
Angular 存储库中有一个问题和一个待处理的 PR:
在最终版本中这实际上很容易做到。我花了一些时间来思考它,因为我遇到的大多数关于它的信息都已经过时了。在这里发布我的解决方案以防其他人遇到这个问题。
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'file-upload',
template: '<input type="file" [multiple]="multiple" #fileInput>'
})
export class FileUploadComponent {
@Input() multiple: boolean = false;
@ViewChild('fileInput') inputEl: ElementRef;
constructor(private http: Http) {}
upload() {
let inputEl: HTMLInputElement = this.inputEl.nativeElement;
let fileCount: number = inputEl.files.length;
let formData = new FormData();
if (fileCount > 0) { // a file was selected
for (let i = 0; i < fileCount; i++) {
formData.append('file[]', inputEl.files.item(i));
}
this.http
.post('http://your.upload.url', formData)
// do whatever you do...
// subscribe to observable to listen for response
}
}
}
然后就这样使用它:
<file-upload #fu (change)="fu.upload()" [multiple]="true"></file-upload>
仅此而已。
或者,捕获事件对象并从 srcElement 获取文件。老实说,不确定哪种方法比另一种更好!
请记住 FormData 是 IE10+,因此如果您必须支持 IE9,则需要一个 polyfill。
更新2017-01-07
更新了代码以处理多个文件的上传。此外,我的原始答案缺少关于 FormData 的一个相当关键的部分(因为我将实际的上传逻辑移动到我自己的应用程序中的单独服务,我在那里处理它)。
这对我有用:Angular 2 对上传文件提供了很好的支持:
<input type="file" (change)="fileChange($event)" placeholder="Upload file" accept=".pdf,.doc,.docx">
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post(URL, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log('success'),
error => console.log(error)
)
}
}
我遇到错误:java.io.IOException: RESTEASY007550: Unable to get boundary for multipart
为了解决这个问题,您应该删除 "Content-Type" "multipart/form-data"
这对我有用:
<input type="file" (change)="onChange($event)" required class="form-control " name="attach_file" id="attach_file">
onChange(event: any) {
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('degree_attachment', file, file.name);
let headers = new Headers();
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post('http://url', formData,options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log('success'),
error => console.log(error)
)
}}
如果您正在寻找一个简单的解决方案并且不想自己编写代码,我建议您使用这个库:
this.uploader.onBeforeUploadItem = function(item) {
item.url = URL.replace('?', "?param1=value1");
}
这个话题非常有用,我觉得有必要分享我的解决方案。 Brother Woodrow's answer was my starting point. I also wanted to draw attention to Rob Gwynn-Jones' 评论 "make sure not to manually set the Content-Type header" 非常重要,为我节省了大量时间。
此版本允许多次 add/remove 操作(来自不同的文件夹),然后一次上传所有文件。
多个同名文件(来自不同文件夹)可以一起上传,但同一个文件不会被添加到上传列表两次(这并不像看起来那么简单!)。
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'file-upload',
template: '<input type="file" [multiple]="multiple" #fileInput>'
})
export class FileUploadComponent {
@Input() multiple: boolean = false;
@ViewChild('fileInput') inputEl: ElementRef;
files: Array<any> = [];
fileObjects: Array<any> = [];
fileKeys: Array<string> = [];
fileCount: number = 0;
constructor(private http: Http) {}
addFiles(callback: any) {
const inputEl: HTMLInputElement = this.inputEl.nativeElement;
const newCount: number = inputEl.files.length;
for (let i = 0; i < newCount; i ++) {
const obj = {
name: inputEl.files[ i ].name,
type: inputEl.files[ i ].type,
size: inputEl.files[ i ].size,
ts: inputEl.files[ i ].lastModifiedDate
};
const key = JSON.stringify(obj);
if ( ! this.fileKeys.includes(key)) {
this.files.push(inputEl.files.item(i));
this.fileObjects.push(obj);
this.fileKeys.push(key);
this.fileCount ++;
}
}
callback(this.files);
}
removeFile(obj: any) {
const key: string = JSON.stringify(obj);
for (let i = 0; i < this.fileCount; i ++) {
if (this.fileKeys[ i ] === key) {
this.files.splice(i, 1);
this.fileObjects.splice(i, 1);
this.fileKeys.splice(i, 1);
this.fileCount --;
return;
}
}
}
}
'addFiles' 中的回调允许在组件外部进行上传。组件是这样使用的:
<file-upload #fu (change)="fu.addFiles(setFiles.bind(this))" [multiple]="true"></file-upload>
'setFiles'是回调。 'this' 在此上下文中是父组件:
setFiles(files: Array<any>) { this.files = files; }
剩下的就是在调用上传之前附加多部分负载 API(也在父组件中):
const formData = new FormData();
for (let i = 0; i < this.files.length; i ++) {
formData.append('file[]', this.files[ i ]);
}
希望这对您有所帮助,如有必要,我们很乐意 fix/update。干杯!
fileUpload() {
const formData = new FormData();
const files = this.filesToUpload;
for (let i = 0; i < files.length; i++) {
formData.append('file', files.item(i));
formData.append('Content-Type', 'application/json');
formData.append('Accept', `application/json`);
}
this.http.post('http://localhost:8080/UploadFile', formData).subscribe(response => console.log(response));
}
然后:
<form (ngSubmit)="upload()">
<input type="file" id="file" multiple (change)="fileUpload($event.target.files)">
<button type="submit">Upload</button>
</form>
我刚刚从 header 中删除 content-type。例如这是我们的 header:
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
'Content-Type': 'multipart/form-data'
});
你所要做的就是从中删除 Content-Type
。喜欢:
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
});
实际上,我正在开发 Spring REST API,其接口编码为 Angular 2.
我的问题是我无法使用 Angular 2.
上传文件我在 java 中的网络资源是:
@RequestMapping(method = RequestMethod.POST, value = "/upload")
public String handleFileUpload(@RequestParam MultipartFile file) {
//Dosomething
}
当我通过 URL 请求使用 Auth header 等调用它时,它工作正常...... (带有 Chrome 的 Advanced Rest Client 扩展)
证明:(在那种情况下一切正常)
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
Spring 配置文件和 Pom 依赖项
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
但是当我尝试用网络表单做同样的事情时:
<input type="file" #files (change)="change(files)"/>
<pre>{{fileContents$|async}}</pre>
使用(更改)方法:
change(file) {
let formData = new FormData();
formData.append("file", file);
console.log(formData);
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
'Content-Type': 'multipart/form-data'
});
this.http.post(this.url, formData, {headers}).map(res => res.json()).subscribe((data) => console.log(data));
/*
Observable.fromPromise(fetch(this.url,
{method: 'post', body: formData},
{headers: this.headers}
)).subscribe(()=>console.log('done'));
*/
}
我的网络服务 return 给我一个错误 500,在 tomcat 日志中:http://pastebin.com/PGdcFUQb
我也尝试了 'Content-Type': undefined
方法但没有成功(网络服务 return 我在这种情况下出现 415 错误。
谁能帮我弄清楚问题出在哪里?
问题已解决,稍后我会用我的代码更新该问题 :) 但是,看看 plunker,它运行良好。 谢谢。
事实上,目前您只能为 Angular2 HTTP 支持的 post
、put
和 patch
方法提供字符串输入。
为此,您需要直接利用 XHR 对象,如下所述:
import {Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Rx';
@Injectable()
export class UploadService {
constructor () {
this.progress$ = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
private makeFileRequest (url: string, params: string[], files: File[]): Observable {
return Observable.create(observer => {
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
for (let i = 0; i < files.length; i++) {
formData.append("uploads[]", files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.upload.onprogress = (event) => {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
}
有关详细信息,请参阅此插件:https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info。
Angular 存储库中有一个问题和一个待处理的 PR:
在最终版本中这实际上很容易做到。我花了一些时间来思考它,因为我遇到的大多数关于它的信息都已经过时了。在这里发布我的解决方案以防其他人遇到这个问题。
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'file-upload',
template: '<input type="file" [multiple]="multiple" #fileInput>'
})
export class FileUploadComponent {
@Input() multiple: boolean = false;
@ViewChild('fileInput') inputEl: ElementRef;
constructor(private http: Http) {}
upload() {
let inputEl: HTMLInputElement = this.inputEl.nativeElement;
let fileCount: number = inputEl.files.length;
let formData = new FormData();
if (fileCount > 0) { // a file was selected
for (let i = 0; i < fileCount; i++) {
formData.append('file[]', inputEl.files.item(i));
}
this.http
.post('http://your.upload.url', formData)
// do whatever you do...
// subscribe to observable to listen for response
}
}
}
然后就这样使用它:
<file-upload #fu (change)="fu.upload()" [multiple]="true"></file-upload>
仅此而已。
或者,捕获事件对象并从 srcElement 获取文件。老实说,不确定哪种方法比另一种更好!
请记住 FormData 是 IE10+,因此如果您必须支持 IE9,则需要一个 polyfill。
更新2017-01-07
更新了代码以处理多个文件的上传。此外,我的原始答案缺少关于 FormData 的一个相当关键的部分(因为我将实际的上传逻辑移动到我自己的应用程序中的单独服务,我在那里处理它)。
这对我有用:Angular 2 对上传文件提供了很好的支持:
<input type="file" (change)="fileChange($event)" placeholder="Upload file" accept=".pdf,.doc,.docx">
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post(URL, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log('success'),
error => console.log(error)
)
}
}
我遇到错误:java.io.IOException: RESTEASY007550: Unable to get boundary for multipart
为了解决这个问题,您应该删除 "Content-Type" "multipart/form-data"
这对我有用:
<input type="file" (change)="onChange($event)" required class="form-control " name="attach_file" id="attach_file">
onChange(event: any) {
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('degree_attachment', file, file.name);
let headers = new Headers();
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post('http://url', formData,options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log('success'),
error => console.log(error)
)
}}
如果您正在寻找一个简单的解决方案并且不想自己编写代码,我建议您使用这个库:
this.uploader.onBeforeUploadItem = function(item) {
item.url = URL.replace('?', "?param1=value1");
}
这个话题非常有用,我觉得有必要分享我的解决方案。 Brother Woodrow's answer was my starting point. I also wanted to draw attention to Rob Gwynn-Jones' 评论 "make sure not to manually set the Content-Type header" 非常重要,为我节省了大量时间。
此版本允许多次 add/remove 操作(来自不同的文件夹),然后一次上传所有文件。
多个同名文件(来自不同文件夹)可以一起上传,但同一个文件不会被添加到上传列表两次(这并不像看起来那么简单!)。
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'file-upload',
template: '<input type="file" [multiple]="multiple" #fileInput>'
})
export class FileUploadComponent {
@Input() multiple: boolean = false;
@ViewChild('fileInput') inputEl: ElementRef;
files: Array<any> = [];
fileObjects: Array<any> = [];
fileKeys: Array<string> = [];
fileCount: number = 0;
constructor(private http: Http) {}
addFiles(callback: any) {
const inputEl: HTMLInputElement = this.inputEl.nativeElement;
const newCount: number = inputEl.files.length;
for (let i = 0; i < newCount; i ++) {
const obj = {
name: inputEl.files[ i ].name,
type: inputEl.files[ i ].type,
size: inputEl.files[ i ].size,
ts: inputEl.files[ i ].lastModifiedDate
};
const key = JSON.stringify(obj);
if ( ! this.fileKeys.includes(key)) {
this.files.push(inputEl.files.item(i));
this.fileObjects.push(obj);
this.fileKeys.push(key);
this.fileCount ++;
}
}
callback(this.files);
}
removeFile(obj: any) {
const key: string = JSON.stringify(obj);
for (let i = 0; i < this.fileCount; i ++) {
if (this.fileKeys[ i ] === key) {
this.files.splice(i, 1);
this.fileObjects.splice(i, 1);
this.fileKeys.splice(i, 1);
this.fileCount --;
return;
}
}
}
}
'addFiles' 中的回调允许在组件外部进行上传。组件是这样使用的:
<file-upload #fu (change)="fu.addFiles(setFiles.bind(this))" [multiple]="true"></file-upload>
'setFiles'是回调。 'this' 在此上下文中是父组件:
setFiles(files: Array<any>) { this.files = files; }
剩下的就是在调用上传之前附加多部分负载 API(也在父组件中):
const formData = new FormData();
for (let i = 0; i < this.files.length; i ++) {
formData.append('file[]', this.files[ i ]);
}
希望这对您有所帮助,如有必要,我们很乐意 fix/update。干杯!
fileUpload() {
const formData = new FormData();
const files = this.filesToUpload;
for (let i = 0; i < files.length; i++) {
formData.append('file', files.item(i));
formData.append('Content-Type', 'application/json');
formData.append('Accept', `application/json`);
}
this.http.post('http://localhost:8080/UploadFile', formData).subscribe(response => console.log(response));
}
然后:
<form (ngSubmit)="upload()">
<input type="file" id="file" multiple (change)="fileUpload($event.target.files)">
<button type="submit">Upload</button>
</form>
我刚刚从 header 中删除 content-type。例如这是我们的 header:
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
'Content-Type': 'multipart/form-data'
});
你所要做的就是从中删除 Content-Type
。喜欢:
let headers = new Headers({
'Authorization': 'Bearer ' + this.token,
});