AngularJS1.5でファイルをアップロードする機能を追加したかったのだが、ファイルをアップロードしてもページ遷移はしたくなかったので、$httpを使ってAJAX的にプログラム内から送って、結果を受け取るようにした。

例として定義するモジュール

定義するモジュール名を仮にappとする:

// app.js
  angular.module('app', [])

選択したファイルをAngularJSに渡せるようにするためのディレクティブを定義する

HTMLのinput type="file"で選択したファイルをAngularJSに受け渡すためのディレクティブを定義する:

// app.js
  angular.module('app')
    .directive('fileModel', ['$parse', function($parse) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          const model = $parse(attrs.fileModel)
          element.bind('change', function() {
            scope.$apply(() => {
              model.assign(scope, element[0].files[0])
            })
          })
        },
      }
    }])
  • elementchangeを監視して、変更されたらモデルを更新する

ファイルを選択してアップロードするためのHTML

上で定義したfile-modelディレクティブを使って、ファイルを選択するinputと、アップロードするためのボタンを用意する:

// index.html
<!DOCTYPE html>
<html ng-app="app">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
    <script src="app.js" type="text/javascript"></script>
  </head>
  <body ng-controller="AppController as appCtrl">
    <input type="file" file-model="myFile">
    <button ng-click="myFile&&appCtrl.upload(myFile)" ng-disabled="!myFile">Upload</button>
  </body>
</html>
  • コントローラAppControllerは後述
  • 選択したファイルをmyFileというモデル(変数名)に格納することにする
  • ボタンが押されたらappCtrluploadメソッドに、選択されたファイルmyFileを渡す

アップロードするサービス

ファイルを$httpで送る機能をAngularJSのサービスとして作成する:

// app.js
  angular.module('app')
    .service('fileUpload', ['$http', function($http) {
      this.uploadFileToUrl = function(uploadUrl, name, file) {
        const formData = new FormData()
        formData.append(name, file)
        const config = {
          headers: {
            'Content-Type': undefined,
          },
        }
        return $http.post(uploadUrl, formData, config)
      }
    }])
  • $http.postを使う
  • Content-Typeundefinedを指定すると良きに計らってくれるらしい

ファイルをアップロードする

上で作成したファイルをアップロードするサービスを使って、コントローラ内からファイルをアップロードする:

// app.js
  angular.module('app')
    .controller('AppController', ['fileUpload', function(fileUpload) {
      this.upload = (file) => {
        fileUpload.uploadFileToUrl('/upload', 'upName', file)
          .then(
            (response) => {
              console.log('upload success')
              console.log(response.data)
            },
            (response) => {
              console.error('upload failed')
              console.log(response)
            })
      }
    }])
  • 依存関係にfileUploadを含めて、それを使う