├── .gitignore ├── README.md ├── app ├── boot.ts ├── components │ ├── app │ │ ├── app.html │ │ └── app.ts │ ├── home │ │ ├── home.html │ │ └── home.ts │ └── login │ │ ├── login.html │ │ ├── login.ts │ │ └── user.ts └── plugins │ └── multipart-upload │ ├── multipart-item.ts │ └── multipart-uploader.ts ├── index.html ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | *.js.map 3 | .idea 4 | app/**/*.js 5 | app/**/*.js.map 6 | node_modules 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-multipartForm 2 | A project for Angular 2 apps to upload multipart/form-data. 3 | 4 | It is something similar to the [ng2-file-upload](https://github.com/valor-software/ng2-file-upload) which only upload file. 5 | 6 | 7 | # How to start 8 | ``` 9 | git clone https://github.com/wangzilong/angular2-multipartForm.git 10 | cd angular2-multipartForm 11 | npm install 12 | # run 13 | npm start 14 | ``` 15 | # Input File in Angularjs 2 16 | home.html 17 | ``` 18 | 19 | ``` 20 | home.ts 21 | ``` 22 | selectFile($event): void { 23 | var inputValue = $event.target; 24 | this.file = inputValue.files[0]; 25 | console.debug("Input File name: " + this.file.name + " type:" + this.file.size + " size:" + this.file.size); 26 | } 27 | ``` 28 | 29 | # Upload multipart/form-data 30 | home.ts 31 | ``` 32 | this.upload = () => { 33 | if (null == this.file || null == this.email || null == this.password){ 34 | console.error("home.ts & upload() form invalid."); 35 | return; 36 | } 37 | // init form item 38 | if (this.multipartItem == null){ 39 | this.multipartItem = new MultipartItem(this.uploader); 40 | } 41 | if (this.multipartItem.formData == null) 42 | this.multipartItem.formData = new FormData(); 43 | 44 | // fill field of form into formData. 45 | this.multipartItem.formData.append("email", this.email); 46 | this.multipartItem.formData.append("password", this.password); 47 | this.multipartItem.formData.append("file", this.file); 48 | 49 | // set callback function 50 | this.multipartItem.callback = this.uploadCallback; 51 | 52 | // upload 53 | this.multipartItem.upload(); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /app/boot.ts: -------------------------------------------------------------------------------- 1 | import {bootstrap} from 'angular2/platform/browser' 2 | import {AppComponent} from './components/app/app' 3 | 4 | import {ROUTER_PROVIDERS} from 'angular2/router'; 5 | 6 | bootstrap(AppComponent,[ 7 | ROUTER_PROVIDERS 8 | ]); -------------------------------------------------------------------------------- /app/components/app/app.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /app/components/app/app.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; 3 | 4 | 5 | import {HomeComponent} from '../home/home'; 6 | import {LoginComponent} from '../login/login'; 7 | 8 | @Component({ 9 | selector: 'my-app', 10 | template: ` 11 | 15 |
16 | 17 | `, 18 | directives: [ROUTER_DIRECTIVES] 19 | }) 20 | 21 | @RouteConfig([ 22 | { path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true }, 23 | { path: '/login', name: 'Login', component: LoginComponent} 24 | ]) 25 | 26 | export class AppComponent { 27 | } 28 | -------------------------------------------------------------------------------- /app/components/home/home.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /app/components/home/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | 3 | import {RouteParams, ROUTER_DIRECTIVES} from 'angular2/router'; 4 | import {MultipartItem} from "../../plugins/multipart-upload/multipart-item"; 5 | import {MultipartUploader} from "../../plugins/multipart-upload/multipart-uploader"; 6 | 7 | const URL = 'http://www.example.com/rest/upload/avatar'; 8 | 9 | @Component({ 10 | selector: 'home', 11 | templateUrl: 'app/components/home/home.html' 12 | }) 13 | export class HomeComponent { 14 | private uploader:MultipartUploader = new MultipartUploader({url: URL}); 15 | 16 | multipartItem:MultipartItem = new MultipartItem(this.uploader); 17 | 18 | email:string; 19 | password:string; 20 | file: File; 21 | 22 | upload : () => void; 23 | uploadCallback : (data) => void; 24 | 25 | constructor(){ 26 | this.upload = () => { 27 | console.debug("home.ts & upload() ==>"); 28 | if (null == this.file || null == this.email || null == this.password){ 29 | console.error("home.ts & upload() form invalid."); 30 | return; 31 | } 32 | if (this.multipartItem == null){ 33 | this.multipartItem = new MultipartItem(this.uploader); 34 | } 35 | if (this.multipartItem.formData == null) 36 | this.multipartItem.formData = new FormData(); 37 | 38 | this.multipartItem.formData.append("email", this.email); 39 | this.multipartItem.formData.append("password", this.password); 40 | this.multipartItem.formData.append("file", this.file); 41 | 42 | this.multipartItem.callback = this.uploadCallback; 43 | this.multipartItem.upload(); 44 | } 45 | 46 | this.uploadCallback = (data) => { 47 | console.debug("home.ts & uploadCallback() ==>"); 48 | this.file = null; 49 | if (data){ 50 | console.debug("home.ts & uploadCallback() upload file success."); 51 | }else{ 52 | console.error("home.ts & uploadCallback() upload file false."); 53 | } 54 | } 55 | 56 | 57 | } 58 | 59 | selectFile($event): void { 60 | var inputValue = $event.target; 61 | if( null == inputValue || null == inputValue.files[0]){ 62 | console.debug("Input file error."); 63 | return; 64 | }else { 65 | this.file = inputValue.files[0]; 66 | console.debug("Input File name: " + this.file.name + " type:" + this.file.size + " size:" + this.file.size); 67 | } 68 | } 69 | 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /app/components/login/login.html: -------------------------------------------------------------------------------- 1 | Login component 2 | -------------------------------------------------------------------------------- /app/components/login/login.ts: -------------------------------------------------------------------------------- 1 | import {Component} from 'angular2/core'; 2 | 3 | import {RouteParams, ROUTER_DIRECTIVES, Router} from 'angular2/router'; 4 | import {NgForm} from 'angular2/common'; 5 | import {User} from './user'; 6 | 7 | @Component({ 8 | selector: 'login', 9 | templateUrl: 'app/components/login/login.html' 10 | }) 11 | export class LoginComponent { 12 | 13 | user = new User('111','222'); 14 | 15 | constructor(public router: Router) { 16 | } 17 | 18 | submitted = false; 19 | 20 | onSubmit() { this.submitted = true; } 21 | 22 | login(event, name, password) { 23 | console.log("login name: " + name); 24 | console.log("login password: " + password); 25 | } 26 | 27 | get diagnostic() { 28 | return JSON.stringify(this.user); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/components/login/user.ts: -------------------------------------------------------------------------------- 1 | export class User{ 2 | name: string; 3 | password: string; 4 | 5 | constructor(name, password) { 6 | this.name = name; 7 | this.password = password; 8 | } 9 | } -------------------------------------------------------------------------------- /app/plugins/multipart-upload/multipart-item.ts: -------------------------------------------------------------------------------- 1 | import {MultipartUploader} from "./multipart-uploader"; 2 | 3 | export class MultipartItem { 4 | public alias:string = 'file'; 5 | public url:string = '/'; 6 | public method:string = 'POST'; 7 | public headers:any = []; 8 | public withCredentials:boolean = true; 9 | public formData:FormData = null; 10 | public isReady:boolean = false; 11 | public isUploading:boolean = false; 12 | public isUploaded:boolean = false; 13 | public isSuccess:boolean = false; 14 | public isCancel:boolean = false; 15 | public isError:boolean = false; 16 | public progress:number = 0; 17 | public index:number = null; 18 | public callback:Function = null; 19 | 20 | constructor(private uploader:MultipartUploader) { 21 | } 22 | 23 | public upload() { 24 | try { 25 | console.debug("multipart-item.ts & upload() ==>."); 26 | this.uploader.uploadItem(this); 27 | } catch (e) { 28 | //this.uploader._onCompleteItem(this, '', 0, []); 29 | //this.uploader._onErrorItem(this, '', 0, []); 30 | } 31 | } 32 | 33 | public init(){ 34 | this.isReady = false; 35 | this.isUploading = false; 36 | this.isUploaded = false; 37 | this.isSuccess = false; 38 | this.isCancel = false; 39 | this.isError = false; 40 | this.progress = 0; 41 | this.formData = null; 42 | this.callback = null; 43 | } 44 | 45 | public onBeforeUpload() { 46 | } 47 | 48 | public onProgress(progress:number) { 49 | } 50 | 51 | public onSuccess(response:any, status:any, headers:any) { 52 | } 53 | 54 | public onError(response:any, status:any, headers:any) { 55 | } 56 | 57 | public onCancel(response:any, status:any, headers:any) { 58 | } 59 | 60 | public onComplete(response:any, status:any, headers:any) { 61 | this.callback(response); 62 | this.init(); 63 | } 64 | 65 | private _onBeforeUpload() { 66 | this.isReady = true; 67 | this.isUploading = true; 68 | this.isUploaded = false; 69 | this.isSuccess = false; 70 | this.isCancel = false; 71 | this.isError = false; 72 | this.progress = 0; 73 | this.onBeforeUpload(); 74 | } 75 | 76 | private _onProgress(progress:number) { 77 | this.progress = progress; 78 | this.onProgress(progress); 79 | } 80 | 81 | private _onSuccess(response:any, status:any, headers:any) { 82 | this.isReady = false; 83 | this.isUploading = false; 84 | this.isUploaded = true; 85 | this.isSuccess = true; 86 | this.isCancel = false; 87 | this.isError = false; 88 | this.progress = 100; 89 | this.index = null; 90 | this.onSuccess(response, status, headers); 91 | } 92 | 93 | private _onError(response:any, status:any, headers:any) { 94 | this.isReady = false; 95 | this.isUploading = false; 96 | this.isUploaded = true; 97 | this.isSuccess = false; 98 | this.isCancel = false; 99 | this.isError = true; 100 | this.progress = 0; 101 | this.index = null; 102 | this.onError(response, status, headers); 103 | this.callback(response); 104 | } 105 | 106 | private _onCancel(response:any, status:any, headers:any) { 107 | this.isReady = false; 108 | this.isUploading = false; 109 | this.isUploaded = false; 110 | this.isSuccess = false; 111 | this.isCancel = true; 112 | this.isError = false; 113 | this.progress = 0; 114 | this.index = null; 115 | this.onCancel(response, status, headers); 116 | } 117 | 118 | private _onComplete(response:any, status:any, headers:any) { 119 | this.onComplete(response, status, headers); 120 | } 121 | 122 | private _prepareToUploading() { 123 | this.isReady = true; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/plugins/multipart-upload/multipart-uploader.ts: -------------------------------------------------------------------------------- 1 | import {MultipartItem} from "./multipart-item"; 2 | export class MultipartUploader { 3 | public url:string; 4 | public authToken:string; 5 | public isUploading:boolean = false; 6 | public progress:number = 0; 7 | public isHTML5:boolean = true; 8 | public timeout:number = 10000; 9 | 10 | constructor(public options:any) { 11 | // Object.assign(this, options); 12 | this.url = options.url; 13 | this.authToken = options.authToken; 14 | } 15 | 16 | public uploadItem(item:MultipartItem) { 17 | console.debug("multipart-uploader.ts & uploadItem() ==>."); 18 | if (this.isUploading) { 19 | console.debug("multipart-uploader.ts & uploadItem() uploader is uploading now."); 20 | return; 21 | } 22 | this.isUploading = true; 23 | this._xhrTransport(item); 24 | } 25 | 26 | private _onBeforeUploadItem(item:any) { 27 | item._onBeforeUpload(); 28 | } 29 | 30 | 31 | private _parseHeaders(headers:any) { 32 | let parsed:any = {}, key:any, val:any, i:any; 33 | 34 | if (!headers) { 35 | return parsed; 36 | } 37 | 38 | headers.split('\n').map((line:any) => { 39 | i = line.indexOf(':'); 40 | key = line.slice(0, i).trim().toLowerCase(); 41 | val = line.slice(i + 1).trim(); 42 | 43 | if (key) { 44 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 45 | } 46 | }); 47 | 48 | return parsed; 49 | } 50 | 51 | private _transformResponse(response:any, headers:any):any { 52 | return response; 53 | } 54 | 55 | private _isSuccessCode(status:any) { 56 | return (status >= 200 && status < 300) || status === 304; 57 | } 58 | 59 | private _render() { 60 | // todo: ? 61 | } 62 | 63 | _xhrTransport(item:any) { 64 | console.debug("multipart-uploader.ts & _xhrTransport() ==>."); 65 | 66 | let xhr = item._xhr = new XMLHttpRequest(); 67 | xhr.timeout = this.timeout; 68 | 69 | //if (item.formData.length === 0){ 70 | // throw new TypeError('Invalid form,form is empty.'); 71 | //} 72 | 73 | this._onBeforeUploadItem(item); 74 | 75 | xhr.upload.onprogress = (event) => { 76 | }; 77 | 78 | xhr.onload = () => { 79 | console.debug("multipart-uploader.ts & _xhrTransport.onload() ==>"); 80 | let headers = this._parseHeaders(xhr.getAllResponseHeaders()); 81 | let response = this._transformResponse(xhr.response, headers); 82 | let gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error'; 83 | let method = '_on' + gist + 'Item'; 84 | (this)[method](item, response, xhr.status, headers); 85 | this._onCompleteItem(item, response, xhr.status, headers); 86 | }; 87 | 88 | xhr.onerror = () => { 89 | console.debug("multipart-uploader.ts & _xhrTransport.onerror() ==>"); 90 | let headers = this._parseHeaders(xhr.getAllResponseHeaders()); 91 | let response = this._transformResponse(xhr.response, headers); 92 | this._onErrorItem(item, response, xhr.status, headers); 93 | //this._onCompleteItem(item, response, xhr.status, headers); 94 | }; 95 | 96 | xhr.ontimeout = () => { 97 | console.debug("multipart-uploader.ts & _xhrTransport.ontimeout() ==>"); 98 | let headers = this._parseHeaders(xhr.getAllResponseHeaders()); 99 | let response = this._transformResponse(xhr.response, headers); 100 | this._onErrorItem(item, response, xhr.status, headers); 101 | //this._onCompleteItem(item, response, xhr.status, headers); 102 | }; 103 | 104 | xhr.onabort = () => { 105 | console.debug("multipart-uploader.ts & _xhrTransport.onabort() ==>"); 106 | let headers = this._parseHeaders(xhr.getAllResponseHeaders()); 107 | let response = this._transformResponse(xhr.response, headers); 108 | //this._onCancelItem(item, response, xhr.status, headers); 109 | this._onCompleteItem(item, response, xhr.status, headers); 110 | }; 111 | 112 | xhr.open(item.method, this.url, true); 113 | xhr.withCredentials = item.withCredentials; 114 | 115 | if (this.authToken) { 116 | xhr.setRequestHeader('Authorization', this.authToken); 117 | } 118 | console.debug("multipart-uploader.ts & _xhrTransport() send..."); 119 | xhr.send(item.formData); 120 | this._render(); 121 | } 122 | 123 | public onSuccessItem(item:any, response:any, status:any, headers:any) { 124 | } 125 | 126 | public onErrorItem(item:any, response:any, status:any, headers:any) { 127 | this.isUploading = false; 128 | } 129 | 130 | public onCancelItem(item:any, response:any, status:any, headers:any) { 131 | } 132 | 133 | public onCompleteItem(item:any, response:any, status:any, headers:any) { 134 | } 135 | 136 | private _onSuccessItem(item:any, response:any, status:any, headers:any) { 137 | item._onSuccess(response, status, headers); 138 | this.onSuccessItem(item, response, status, headers); 139 | } 140 | 141 | public _onErrorItem(item:any, response:any, status:any, headers:any) { 142 | console.debug("multipart-uploader.ts & _onErrorItem() ==>" + " Error status:" + status); 143 | item._onError(response, status, headers); 144 | this.onErrorItem(item, response, status, headers); 145 | } 146 | 147 | private _onCancelItem(item:any, response:any, status:any, headers:any) { 148 | item._onCancel(response, status, headers); 149 | this.onCancelItem(item, response, status, headers); 150 | } 151 | 152 | public _onCompleteItem(item:any, response:any, status:any, headers:any) { 153 | item._onComplete(response, status, headers); 154 | this.onCompleteItem(item, response, status, headers); 155 | 156 | this.isUploading = false; 157 | 158 | //this.progress = this._getTotalProgress(); 159 | this._render(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular 2 QuickStart 5 | 6 | 7 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 | 39 | 40 | 41 | 42 | Loading... 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-form", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "tsc": "tsc", 6 | "tsc:w": "tsc -w", 7 | "lite": "lite-server", 8 | "start": "concurrent \"npm run tsc:w\" \"http-server\" " 9 | }, 10 | "license": "ISC", 11 | "dependencies": { 12 | "angular2": "2.0.0-beta.0", 13 | "systemjs": "0.19.6", 14 | "es6-promise": "^3.0.2", 15 | "es6-shim": "^0.33.3", 16 | "reflect-metadata": "0.1.2", 17 | "rxjs": "5.0.0-beta.0", 18 | "zone.js": "0.5.10" 19 | }, 20 | "devDependencies": { 21 | "concurrently": "^1.0.0", 22 | "lite-server": "^1.3.1", 23 | "typescript": "^1.7.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "system", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": false 11 | }, 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------