Nuxt.jsプロジェクトでFirebase Storageにアップロードした画像を変更/削除する

はじめに

数回に分けてNuxt.jsプロジェクトでデータの永続化を行う方法を解説しています。これらの記事は上から順番に読んでいくことを想定しています。

これらの記事で作成したNuxt.jsプロジェクトは以下のGitHubリポジトリーで公開しています。

GitHub - whitia/nuxt-firebase-sample

前回はユーザーごとに画像ファイルをつけられるようにしました。その画像ファイルはFirebase Storageに保存しました。今回はその続きで、Firebase Storageに保存した画像ファイルを変更/削除する方法について解説します。

実装

諸事情により、まずは画像ファイルを削除する処理を作っていきます。

pages/users/index.vueに以下を追記します。

<!-- テンプレートは変更なし -->

export default {
  ...

  methods: {
    deleteUser(id) {
      if (!confirm('Are you sure?')) return

      this.$store.dispatch('deleteUser', { id })
      .then(() => {
        setTimeout(() => {
          this.$store.dispatch('fetchUsers')
        }, 1000)
      })

      // 以下を追記
      this.$store.dispatch('deleteFile', { name: id })
    }
  }
}

ユーザー情報を削除した後、画像ファイルを削除するためにVuexストアのdeleteFileアクションを呼び出します。

store/index.jsに以下を追記します。

...

export const actions = {
  ...

  // 以下を追記
  deleteFile({ commit }, payload) {
    return new Promise((resolve, reject) => {
      firestorage.ref('images/').child(payload.name).delete()
      .then(() => {
        resolve(true)
      })
      .catch(error => {
        console.log('An error occurred in deleteFile(): ', error)
        reject(error)
      })
    })
  }
}

...
}

deleteFileアクションはFirebase Storageの画像ファイルを削除する処理ですね。

削除処理はこれで実装完了です。ユーザー一覧画面からユーザーを削除したときに、画像ファイルも一緒に削除されているか確認してみてください。

続いて変更処理です。と言っても、Firebase Storageに保存したファイルを変更する手段はありません。ですので、通常は削除→新規という流れで処理を書いていきます。

pages/users/edit/_id.vueに以下を追記します。

<template>
  <b-form @submit.prevent="editUser">
    <div class="container mt-5">
      ...

      <!-- 以下を追記 -->
      <div class="row justify-content-center">
        <div class="col-12 col-sm-3">
          Avatar
          <b-form-file
            placeholder="画像ファイルを選択してください"
            drop-placeholder="画像ファイルをドラッグ&ドロップしてください"
            accept="image"
            id="avatar"
            class="mb-3"
            required
            plain
          ></b-form-file>
          <img :src="$store.getters.getUser.avatar" class="img-fluid rounded-circle" />
        </div>
      </div>

      ...
    </div>
  </b-form>
</template>

export default {
  ...

  methods: {
    editUser(e) {
      const user = {
        // 以下を修正
        oldId: this.$store.getters.getUser.id,
        newId: this.$store.getters.getUser.id,
        name: {
          first: e.target.first.value,
          last: e.target.last.value
        },
        age: e.target.age.value,
        // 以下を追記
        avatar: e.target.avatar.files[0]
      }

      // 以下を修正
      if (user.avatar) {
        this.$store.dispatch('deleteFile', { name: user.oldId })

        this.$store.dispatch('uploadFile', {
          file: user.avatar
        })
        .then(response => {
          user.newId = response.name
          user.avatar = response.url
          this.$store.dispatch('editUser', { user })
          .then(() => {
            setTimeout(() => {
              this.$router.push('/users')
            }, 1000)
          })
        })
      } else {
        user.avatar = this.$store.getters.getUser.avatar
        this.$store.dispatch('editUser', { user })
        .then(() => {
          setTimeout(() => {
            this.$router.push('/users')
          }, 1000)
        })
      }
    }
  }
}

まず前提として、ファイル選択フォームはセキュリティリスクがあるためデータをバインドすることができません。ですので、初期状態は必ず未選択状態になっています。editUserメソッドではuser.avatarが選択されているか判定しています。ファイルが選択されていれば現在の画像を削除→新しい画像を保存し、選択されていなければユーザー情報のみを更新しています。

呼び出すアクションはすでに実装済みのアクションなので追加はありません。しかし、editUserアクションを少しだけ修正する必要があります。store/index.jsを修正します。

...

export const actions = {
  ...

  editUser({ commit }, payload) {
    return new Promise((resolve, reject) => {
      // 以下を修正
      usersRef.where('id', '==', payload.user.oldId).get()
      .then(snapshot => {
        snapshot.forEach(doc => {
          const user = {
            // 以下を修正
            id: payload.user.newId,
            name: payload.user.name,
            age: payload.user.age,
            // 以下を追記
            avatar: payload.user.avatar,
            updated_at: firebase.firestore.FieldValue.serverTimestamp()
          }

          usersRef.doc(doc.id).update(user)
          .then(ref => {
            resolve(true)
          })
          .catch(error => {
            console.error('An error occurred in editUser(): ', error)
            resolve(error)
          })
        })
      })
    })
  },

  ...
}

...

画像ファイルが新しくなった場合はユーザー情報のidも更新します。しかし実装してから思いましたが、一度作成したデータのidを後から変更するのは設計思想的にあまりよろしくない気がします。本筋とは離れるので今回は気にしないことにします。

テストサーバーを起動し、ブラウザで動作を確認してみてください。ユーザーの画像を変更した場合、古い画像が削除され、新しい画像に差し替わっているでしょうか。

まとめ

Firebase Storageにアップロードした画像の変更/削除について解説しました。

Firebase Storageを使ってみた感想として、レスポンスが遅い気がしました。Firebase Storageがというより、Firebase全体が。Firebaseは近々Google Cloud Platformと統合されるという噂があります。GCPのほうが多機能でレスポンスも早いので、あえてFirebaseを使う必要はあまりないかもしれませんね...