[ Angular2 ] 이미지 파일 Input 및 preview

file input 필드에 이미지를 선택하여 입력하면, 이미지 preview 를 출력하는 방법을 간략히 적어본다.

바로 해보자.

//Angular Cli 를 이용하여 새 프로젝트를 생성한다.
$ ng new img-input

 

src/app/app.component.html

<h1>
  {{title}}
</h1>

<input type='file' accept="image/*" (change)="readUrl($event)" multiple>
<img *ngFor="let u of dataUrl" [src]="u" width="300px">

 

src/app/app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';

  dataUrl: any[] =[];
  readUrl($event) {
    for ( var i=0; i<$event.target.files.length; i++){
      var reader = new FileReader();

      reader.onload = (loadEvent: any) => {
        this.dataUrl.push(loadEvent.target.result);
      }

      reader.readAsDataURL($event.target.files[i]);
    }
  }
}

위 코드 대로 ng serve 로 실행해보면, 하나 또는 복수의 이미지를 선택했을 때, 이미지 수 만큼의 Preview가 즉시 나타난다.

핵심 개념: File to Base64

입력된 파일을 Base64 형식의 데이타로 변환해주는 작업을 거치면, 바로 표현할 수 있는 이미지 src 가 된다. 다른말로 dataUrl 이라고 부른다. readUrl($event) 함수가 그 기능을 담당한다.

var reader = new FileReader();

reader.onload = (loadEvent: any) => {
  this.dataUrl.push(loadEvent.target.result);
}

reader.readAsDataURL($event.target.files[i]);

 

이미지가 여러 개인 것을 가정했으므로, dataUrl 은 array로 잡아준다. input 필드에 change 이벤트가 발생하면, readUrl 함수를 호출하고 FileReader()를 사용해 파일을 readAsDataURL 로 변환해준다. 변환이 끝나면 FileReader 의 onloadend 나 onload 메서드를 사용해서 dataUrl 항렬(array)에 element로 추가해준다.

마지막으로 ngFor loop 을 활용해서 dataUrl 에 들어있는 이미지 데이타를 이미지로 표현하여 나열해주었다.

Angular2 이미지 파일 처리 Base64 DataUrl의 활용

위에 설명된 개념을 활용해서 cropper.js 를 Angular2 에서 사용할 수 있도록 Directive 를 만들어보자. 이 글의 초점은 이미지 파일을 어떻게 보여주느냐에 있기 때문에 cropper.js 의 사용법이나 구체적인 활용법에 대해서는 생략한다. 단지, Cropperjs 의 캔버스가 화면에 나타나도록하는 간단한 Directive 를 만들어보는 것이 목적이다. ( ※ 물론, 이것을 기반으로 데이타바인딩과 이벤트바인딩 등을 활용하면 cropperjs를 제대로 활용할 수 있는 애플리케이션 개발도 가능하다. )

먼저, angular cli 로 새로운 프로젝트를 생성해보자.

$ ng new testcropper

$ cd testcropper

cropperjs 를 설치하자.

$ npm install cropperjs --save
$ npm install @types/cropperjs --save

MyCropperDirective (my-cropper.directive.ts) 를 생성하자.

$ ng new directive my-cropper

my-cropper.directive.ts 에서 import * as Cropper from ‘cropperjs’ 를 상단 import 자리에 삽입한다. nodejs 에서  var cropper = require(‘cropperjs’) 와 같은 기능을 한다.

src/app/my-cropper.directive.ts

import { Directive, Input, ElementRef } from '@angular/core';
import * as Cropper from 'cropperjs';

@Directive({
  selector: '[my-cropper]'
})
export class MyCropperDirective {

  @Input('options') options: any = {};
  @Input('src') src: string;
  private cropper;

  constructor(
    private _element: ElementRef,
  ) {
  }

  ngOnInit(): void {
    this._element.nativeElement.src = this.src;
    this.cropper = new Cropper(this._element.nativeElement, this.options);
  }

}

@Input 으로 option 과 src 를 parent component 에서 불러들인다.

src/app/app.component.html

<h1>
  {{title}}
</h1>
<input type="file" (change)="readUrl($event)">
<img *ngFor="let u of dataUrl" my-cropper [options]="cropperOptions" [src]="u"/>

여기서도 이 글 전반에서 설명한 dataUrl array 와 readUrl($event) 함수가 그대로 쓰였다. 파일 Input 필드에서 이미지 파일을 선택하여 입력하면 realUrl($event)가 호출되어 파일을 DataUrl (base64) 형식으로 읽어들인다. dataUrl array는 my-cropper 지시자가 부여된 img 태그 필드,  src 속성값 바인딩을 통해 MyCropperDirective 로 전달된다.

src/app/app.component.ts

import { Component, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  dataUrl:any[]=[];

  cropperOptions: any = {
    minContainerWidth: 400,
    minContainerHeight: 300,
    aspectRatio: 16 / 9,
    crop: function (e) {
      console.log(e.detail.x);
      console.log(e.detail.y);
      console.log(e.detail.width);
      console.log(e.detail.height);
      console.log(e.detail.rotate);
      console.log(e.detail.scaleX);
      console.log(e.detail.scaleY);      
    }
  };

  readUrl($event) {
    if ($event.target.files.length > 0) {
      var reader = new FileReader();
      var that = this;
      reader.onload = (loadEvent: any) => {
        that.dataUrl.push(loadEvent.target.result);
      }

      reader.readAsDataURL($event.target.files[0]);
    }
  }

}

cropperOptions 값은 cropperjs 사이트에 나와있는 값을 그대로 가져와서 사용했다. ng serve로 애플리케이션을 실행하면, cropper box를 드래그하는 대로 console 창에 좌표값이 출력되는 것을 확인할 수 있다.

개념 확장

보통 dataUrl 로 변환된 후에 crop 이나 image resize 같은 처리를 하게 되는데, 처리된 데이타를 서버로 전송하는 일도 또하나의 숙제다. 간단하게 개념을 정리하면, 다음과 같다.

  • 파일의 형태로 재변환하여 전송하는 법 (multi-part)
    file -> dataUrl -> 데이타처리 -> blob ->( 폼 데이타에 파일처럼 append 하여 서버로 전송)
    ※ blob 으로 변환되에 전송된 데이타는 서버에서는 파일과 동일하게 처리된다.
  • Base64 형식으로 바로 전송하는 법
    file -> dataUrl -> 데이타처리 -> ( dataUrl의 형식으로 서버에 전송 ) ->서버에서 데이타를 파일로 변환하여 저장하거나 dataUrl 형식 그대로 database 에 저장
    ※ dataUrl 은 그냥 base64 형식의 string이다. 그래서 database에 바로 저장이 가능하다. 물론, database 특성에 맞게 sanitizing 을 해야하는 경우도 있다. 

자세한 처리과정은 다음 기회에 정리해 보려한다.

 

맺음말

애플리케이션 개발시에 이미지나 파일 처리하는 법을 잘 알아야한다. 하지만, 공부하는 입장에서 관련 자료를 찾아보면, 제대로 된 자료를 찾기 힘들다.

Dropzone.js  같은 외부 유틸리티를 이용한 Angular2 wrapper 가 만들어져있다면, 참 편할 것 같다. 그런데, 쓸만한 것들을 찾기가 쉽지 않다. 아직은 Angular2  를 위한 기반이 부족한 것 같다.

 

댓글 남기기