Translate

[Angular 2+] Form Submit 할 시 파일 포함하기 (multipart) - File Upload





Angular 로 파일 업로드 시 아래의 모듈을 많이 사용하는 것 같다.

1. ng2-file-upload
  - https://github.com/valor-software/ng2-file-upload

2. ng2-fileupload
  - https://github.com/thinkholic/ng2-fileupload


Angular 4

모듈이라는 개념에 맞게 순수하게 파일 업로드 기능만 들어있다.
저 모듈을 이용하게되면 우선 파일을 업로드 후 form submit 을 시켜야 할 것으로 보여진다.

서버 API 작업 시 multipart 로 text/file 파라메터를 동시에 받아 작업하는게 편한 경우가 있어 한번에 submit 시킬 수 있도록 작업을 했다.


작업환경: Angular 4

1004lucifer

multipart-form.service.ts
import {Injectable} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {Headers, Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class MultipartFormService {

    constructor(
        private http: Http
    ) {
    }

    /**
     * Form의 string / File 파라메터를 Multipart 로 묶어서 서버에 전송한다.
     * @param {string} url
     * @param {FormGroup} formGroup
     * @param {Array<{name: string; file: File}>} files - [{'name': 'fileKey1', 'file': this.file1}, ...]
     * @returns {Observable<Response>}
     */
    sendMultipartForm(url: string, formGroup: FormGroup, files?: Array<{name: string, file: File}>): Observable<Response> {

        const formData = new FormData();

        for (const key in formGroup.value) {
            if (formGroup.value.hasOwnProperty(key)) {
                formData.append(key, formGroup.value[key]);
            }
        }

        if (files) {
            files
                .filter((file) => file.name != null && file.file != null)  // 정상적이지 않은 객체 Filter
                .forEach((file) => {
                    formData.append(file.name, file.file);
                });
        }

        const headers = new Headers({'enctype': 'multipart/form-data'});
        return this.http.post(url, formData, { headers });
    }
}






사용 (실제 사용된 소스를 약간 수정했다.)


use.component.html
<div>
    <form [formGroup]="myForm" (ngSubmit)="onSubmit()">
        <table class="tbl_w1 mrg_T20">
            <colgroup>
                <col width="150">
                <col width="">
            </colgroup>
            <tr>
                <td>param1</td>
                <td><input formControlName="param1" type="text" class="white_grayline input_wmiddle back_gray"></td>
            </tr>
            <tr>
                <td>param2</td>
                <td><input formControlName="param2" type="text" class="white_grayline input_wmiddle back_gray"></td>
            </tr>
            <tr>
                <td>file1</td>
                <td><input #file1Name class="file_input_textbox2" readonly/>
                    <div class="file_input_div">
                        <input type="button" value="찾아보기" class="file_input_button"/>
                        <input (change)="file1 = extractFile($event);
                                        file1Name.value = extractFileName($event)"
                               type="file" class="file_input_hidden"/>
                    </div>
                </td>
            </tr>
            <tr>
                <td>file2</td>
                <td><input #file2Name class="file_input_textbox2" readonly/>
                    <div class="file_input_div">
                        <input type="button" value="찾아보기" class="file_input_button"/>
                        <input (change)="file2 = extractFile($event);
                                        file2Name.value = extractFileName($event)"
                               type="file" class="file_input_hidden"/>
                    </div>
                </td>
            </tr>
        </table>

        <p class="aln_c mrg_T20">
            <button type="submit">확인</button>
        </p>
    </form>
</div>

1004lucifer

use.component.ts
import {Component, Input, OnInit} from '@angular/core';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {FormControl, FormGroup} from '@angular/forms';
import {MultipartFormService} from './multipart-form.service';

@Component({
    selector: 'use-selector',
    templateUrl: './use.component.html',
    styles: []
})
export class UseComponent implements OnInit {

    myForm: FormGroup;
    file1: File;
    file2: File;

    constructor(
        public activeModal: NgbActiveModal,
        private multipartFormService: MultipartFormService
    ) {
        this.myForm = new FormGroup({
            'param1': new FormControl(),
            'param2': new FormControl()
        });
    }

    ngOnInit() {
    }

    extractFile(event) {
        const files = event.target.files || event.srcElement.files;
        return files.length === 0 ? null : files[0];
    }
    extractFileName(event) {
        const file = this.extractFile(event);
        return file ? file.name : '';
    }

    onSubmit() {
        this.multipartFormService.sendMultipartForm(
            '/api/resourceName',
            this.myForm,

            // files 파라메터는 옵션으로 추가하지 않아도 된다.
            [{'name': 'fileKey1', 'file': this.file1}, {'name': 'fileKey2', 'file': this.file2}],
        ).subscribe(
            (response) => {
                console.log(response.json());
            },
            (response) => {
                console.log(response.json());
            });
    }
    /**
    onSubmit() {
        this.multipartFormService.sendMultipartForm(
            '/api/resourceName',
            this.myForm,
        ).subscribe(
            (response) => {
                console.log(response.json());
            },
            (response) => {
                console.log(response.json());
            });
    }
    */
}



use.module.ts
@NgModule({
    imports: [
        ReactiveFormsModule,
  ...
    ],
    declarations: [UseComponent],
    providers: [
        MultipartFormService
    ],
})
export class UseModule {
}



참고
- http://nberserk.github.io/default/2017/02/12/angular-form-file.html
  (사실상 위의 링크를 보고 만들었다.)
- https://www.angularjs4u.com/angularjs2/10-angular-2-file-upload-demos/





PS.
서버사이드를 Java Spring 을 사용하는데 위와같이 작업 시 이름이 같은 중복 파라메터는 보낼 수 없어 JSON 형식으로 객체를 보내는 방법으로 수정을 했는데 Content-Type 이슈로 인해 Spring 에서 JSON<=>객체 매핑이 제대로 되지 않았다.
아래와 같은 방법으로 Content-Type 헤더를 추가하여 이슈를 해결하였다.

링크: [Angular 2+] multipart 보낼 시 헤더에 Content-type 추가하기


댓글