使用 Ionic 和 Firebase 在三天内开发一个照片分享应用程序 — 创建通信处理(服务、配置 Firebase 工具) —

使用Ionic和Firebase,3天内创建一个图片分享应用程序的系列第6部分,创建通信处理部分。

请点击此处查看汇总文章:
https://qiita.com/kazuki502/items/585aef0d79ed1235bec0

因为是一个应用程序,所以想要与服务器和数据进行交互。

我們之前已經完成了畫面轉換和畫面內的處理(例如照片預覽等)。這次我們將開始實現應用程序的核心功能,即①照片上傳功能和②已上傳照片的下載功能。

使用firebase建立无服务器环境。

为了让大家使用这个应用程序,仅在本地运行是不够的,需要将其上传到服务器并使其能够被访问。但是,搭建自己的服务器并连接到互联网进行设置等工作很麻烦。虽然我曾经做过一次,但如果可能的话,我不想再做这样的工作。

当制作应用程序时,有两个主要障碍,即”环境配置”和”服务器安装”。这两个问题相当繁琐,很多人都会陷入困境。但是现在多亏了许多人的努力,这些问题正在逐渐消失。关于”环境配置”,以前需要手动逐个安装软件,但现在只需使用一个命令就可以自动完成。相对以前,大大提高了便利性。那么”服务器安装”又如何呢?它也变得相当方便了(感谢我们出生在这个时代)。

实际上,我已经在环境设置中进行了应用程序的部署。就是这个部分。如果使用Firebase,就可以在不需要繁琐的服务器设置的情况下使用服务器!这一次我们将使用除了”hosting”之外的其他功能。

使用功能的说明 de

firestorage(文件存储)

这次我们要上传照片,但需要准备上传的目的地。可以想象一下Google Drive或Dropbox这样的服务。这里我们使用的功能是Firestorage。由于可以从应用程序中上传,所以我们将使用它来将照片保存到服务器上。

在中文中,”Firestore(数据库)”可以被表达为 “云端数据模块(数据库)”。

为了获取应用程序上传的照片和消息的信息,需要准备一个数据库。这个功能也包含在Firebase中,它被称为Firestore。这次我们想要使用它作为数据库。
Firestore是一种新型的NoSQL数据库,与通常使用的关系型数据库不同。尽管我们选择使用它是因为它在Firebase中存在,但需要注意的是它并不是最优秀的数据库。如果您想要了解更多详细信息,请参考此链接(https://academy.gmocloud.com/qa/20160509/2284)。

让我们实施吧!

设定环境变量 de

要在应用程序中使用Firestore和FireStorage,需要进行一些设置。首先,安装一个使Firebase更易于使用的模块。本次将使用Angularfire2。

Angularfire2 是一个开源的库,用于将Angular框架与Firebase后端服务集成。该库提供了一套API和工具,方便开发者使用Angular构建实时应用程序,并利用Firebase的实时数据库、身份验证等功能。详细信息可在GitHub上的https://github.com/angular/angularfire2 获取。

请按照 Readme 的说明,在应用所在的本地文件夹中执行写命令来安装模块。

$ npm install firebase @angular/fire --save

请您在安装完成后,将在environments文件夹中的文件如下进行编辑。

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "Your apikey",
    authDomain: "Your authDomain",
    databaseURL: "Your databaseURL",
    projectId: "Your projectId",
    storageBucket: "Your storageBucket",
    messagingSenderId: "Your messagingSenderId",
    appId: "Your appId"
  }
};

请使用从Firebase配置设置中获取的您的值来代替”Your ***”。进入Firebase控制台的项目页面后,点击左侧菜单的”项目概览”旁边的齿轮图标,然后点击”项目设置”,在”常规”选项卡的底部可以找到”Firebase SDK片段”,您可以在这里获取您应该使用的值。

添加模块

请在 app.module.ts 文件中添加 angularfire2 模块。请按照以下方式执行。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

// 追加
import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    // 追加
    AngularFireModule.initializeApp(environment.firebaseConfig),
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

使用angularfire2的准备工作已经完成。

火柜的设置

因为AngularFireStorageModule模块专为firestorage而设计,所以请在app.module.ts中添加该模块。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
// 追加
import { AngularFireStorageModule } from '@angular/fire/storage';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireStorageModule // 追加
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

现在,您可以在应用程序内与firestorage进行通信了。请不要忘记在Firebase侧进行设置。请进入Firebase控制台,点击左侧菜单栏的”Storage”。然后您会看到这样的界面。

image.png

请点击开始指南。

image.png

请点击“确认”。

image.png

如果还保持现状,未经认证的用户将无法上传,因此我想要改变规则。请打开规则选项卡,将规则更改如下。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if true;
    }
  }
}

现在,可以准备将照片上传到服务器了。

上传照片

让我们在应用程序中添加上传照片的处理功能。目前为止,我认为已经可以选择照片并显示预览。

image.png

让我们从这里开始,按下发送按钮以上传图片。直观地,我们可能会想要在upload-photo.component.ts中实现上传处理功能,但也许我们也希望在其他屏幕上使用该功能。此外,将此类通信处理分离出来通常会更加方便。因此,让我们将通信处理整合在一起,分别实现。

为了这个目的,我想使用Service。Service用于将需要作为共同处理的功能,如与服务器的通信和认证等,整理在一起。“如果觉得可能在其他画面上也能用到的话~”请考虑使用Service。首先,请执行以下命令。

$ ionic g service shared/service/network

然后,在shared文件夹中创建了一个service文件夹,并在其中创建了network.service.ts文件。请将其内容编辑如下。

import { Injectable } from '@angular/core';
import { AngularFireStorage } from '@angular/fire/storage';

@Injectable({
  providedIn: 'root'
})
export class NetworkService {

  constructor(
    private storage: AngularFireStorage
  ) { }

    // 写真をアップロードする
    public async uploadPhoto(img: string, filename: string) {
      // 画像が無い場合は処理を終了します。
      if (!img) {
        throw new Error('写真がありません');
      } else {
        const filePath = 'photos/' + filename;
        const ref = this.storage.ref(filePath);
        const task = ref.putString(img, 'data_url');

        return task.then((snapshot) => {
          console.log('Uploaded', snapshot.totalBytes, 'bytes.');
          console.log('File metadata:', snapshot.metadata);
          // Let's get a download URL for the file.
          return snapshot.ref.getDownloadURL().then((url) => {
            return alert(`${url}`);
          });
        });
      }
    }
}

使用”uploadPhoto”功能上载照片后,当成功时会显示照片的url。为了在组件中可以使用它,请将其添加到”app.module.ts”中。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
import { AngularFireStorageModule } from '@angular/fire/storage';

// 追加
import { NetworkService } from 'src/app/shared/service/network.service'

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireStorageModule
  ],
  providers: [
    StatusBar,
    SplashScreen,
    // 追加
    NetworkService,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

将其嵌入到画面处理中

在定义好服务之后,接下来让我们将其集成到组件中。

import { Component, OnInit, Input } from '@angular/core';
// ModalControllerを追加
import { NavParams, ModalController } from '@ionic/angular';
// 追加
import { NetworkService } from 'src/app/shared/service/network.service';

const RESULT = 'result';

@Component({
  selector: 'app-modal-upload',
  templateUrl: './modal-upload.component.html',
  styleUrls: ['./modal-upload.component.scss'],
})
export class ModalUploadComponent implements OnInit {

  // "value" passed in componentProps
  public imageSrc: any;
  public isSelected: boolean;
  @Input() value: number;
  private reader = new FileReader();

  constructor(
    private navParams: NavParams,
    private network: NetworkService, // 追加
    private modalController: ModalController // 追加
  ) { }

  ngOnInit() {}

  public previewPhoto(event) {
    const file = event.target.files[0];
    this.reader.onload = ((e) => {
      this.imageSrc = e.target[RESULT];
      this.isSelected = true;
    });
    this.reader.readAsDataURL(file);
  }

  // 写真アップロード処理
  public async uploadPhoto() {
    if (!this.imageSrc) {
      alert('写真を選択してください');
    } else {
      // ファイル名の作成
      const now = new Date();
      const filename = 'eventname_' + now.getHours() + now.getMinutes() + now.getSeconds() + now.getMilliseconds();
      this.network.uploadPhoto(this.imageSrc, filename).then((value) => {
        this.modalController.dismiss(); // モーダル画面を閉じる処理
      });
    }
  }
}

不要忘记将处理功能与按钮绑定在一起。

<label class="photo" for="photo-upload">
  <div [ngClass]="{'inactive': isSelected}">
    <ion-icon name="image"></ion-icon><br>
    画像を選択
  </div>
  <ion-img [ngClass]="{'inactive': !isSelected}" [src]="imageSrc"></ion-img>
</label>
<input type="file" name="photo" id="photo-upload" accept="image/*" (change)="previewPhoto($event)">

<ion-button class="send" (click)="uploadPhoto()">送信</ion-button>

上传照片后,将显示一个警报,并显示URL。访问此URL将可以看到所上传的照片。此外,查看Firebase控制台,可以看到

image.png

可以看到照片已经被上传了。这样就完成了照片上传功能。

将上传的照片信息保存到数据库中

为了使上传的照片能够在应用程序中使用,需要创建一个保存照片信息的数据库,并将信息存储在其中。首先,我们先进行一些配置来使用Firebase数据库。

在Firebase控制台中,点击”数据库”按钮。

image.png

请点击”创建数据库”。

image.png

请点击选择“开始测试模式”,然后点击“启用”。

image.png

现在可以使用数据库了。

导入数据库模块

返回应用程序,并导入适用于Firestore的模块。将其添加到”app.module.ts”文件中,与以前一样。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AngularFireModule } from '@angular/fire';
import { environment } from '../environments/environment';
import { AngularFireStorageModule } from '@angular/fire/storage';
// 追加
import { AngularFirestoreModule } from '@angular/fire/firestore';

import { NetworkService } from 'src/app/shared/service/network.service'

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireStorageModule,
    // 追加
    AngularFirestoreModule
  ],
  providers: [
    StatusBar,
    SplashScreen,
    NetworkService,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

在服务中添加数据库通信功能

和之前一样,我们希望将与数据库的通信作为共享功能分开,所以让我们将数据库通信处理添加到之前创建的服务中。

import { Injectable } from '@angular/core';
import { AngularFireStorage } from '@angular/fire/storage';
import { AngularFirestore } from '@angular/fire/firestore';

@Injectable({
  providedIn: 'root'
})
export class NetworkService {

  constructor(
    private storage: AngularFireStorage,
    private db: AngularFirestore
  ) { }

  // 写真をアップロードする
  public async uploadPhoto(img: string, filename: string) {
    // 画像が無い場合は処理を終了します。
    if (!img) {
      throw new Error('写真がありません');
    } else {
      const filePath = 'photos/' + filename;
      const ref = this.storage.ref(filePath);
      const task = ref.putString(img, 'data_url');

      return task.then((snapshot) => {
        console.log('Uploaded', snapshot.totalBytes, 'bytes.');
        console.log('File metadata:', snapshot.metadata);
        // Let's get a download URL for the file.
        return snapshot.ref.getDownloadURL().then((url) => {
          return alert(`${url}`);
        });
      });
    }
  }

  // データベースにアップロードされた写真を追加する
  private addPhotoInfo(imgPath: string, photoname: string) {
    return this.db.collection('photolist').add({
      name: photoname,
      url: imgPath,
      createdAt: new Date()
    });
  }
}

addPhotoInfo函数的作用是将上传至数据库的图片路径、图片名称和创建时间写入。只有在图片上传成功时才希望进行数据库的写入操作,因此接下来将编辑uploadPhoto函数。

  // 写真をアップロードする
  public async uploadPhoto(img: string, filename: string) {
    // 画像が無い場合は処理を終了します。
    if (!img) {
      throw new Error('写真がありません');
    } else {
      const filePath = 'photos/' + filename;
      const ref = this.storage.ref(filePath);
      const task = ref.putString(img, 'data_url');

      return task.then((snapshot) => {
        console.log('Uploaded', snapshot.totalBytes, 'bytes.');
        console.log('File metadata:', snapshot.metadata);
        // Let's get a download URL for the file.
        return snapshot.ref.getDownloadURL().then((url) => {
          // return alert(`${url}`);
          // データベース書き込み処理を追加
          return this.addPhotoInfo(url, filename);
        });
      });
    }
  }

好的,我们用这个试试看。现在让我们尝试一下当上传照片时,会不会被写入到数据库中。

image.png

好的,已经正确地写入了。这样,我们也可以将数据写入数据库了。

读取数据库

最后,我想要能够将上传的照片以列表形式获取。虽然我认为你已经大致理解了流程,但正是如此,我将在服务中添加这个功能。

import { Injectable } from '@angular/core';
import { AngularFireStorage } from '@angular/fire/storage';
import { AngularFirestore } from '@angular/fire/firestore';
import { map } from 'rxjs/operators'; // 追加

@Injectable({
  providedIn: 'root'
})
export class NetworkService {

  constructor(
    private storage: AngularFireStorage,
    private db: AngularFirestore
  ) { }

  // 写真をアップロードする
  public async uploadPhoto(img: string, filename: string) {
    // 画像が無い場合は処理を終了します。
    if (!img) {
      throw new Error('写真がありません');
    } else {
      const filePath = 'photos/' + filename;
      const ref = this.storage.ref(filePath);
      const task = ref.putString(img, 'data_url');

      return task.then((snapshot) => {
        console.log('Uploaded', snapshot.totalBytes, 'bytes.');
        console.log('File metadata:', snapshot.metadata);
        // Let's get a download URL for the file.
        return snapshot.ref.getDownloadURL().then((url) => {
          // return alert(`${url}`);
          return this.addPhotoInfo(url, filename);
        });
      });
    }
  }

  // データベースにアップロードされた写真を追加する
  private addPhotoInfo(imgPath: string, photoname: string) {
    return this.db.collection('photolist').add({
      name: photoname,
      url: imgPath,
      createdAt: new Date()
    });
  }

  // 写真リストを取得する:追加
  public getPhotos() {
    return this.db.collection<{createdAt: Date, name: string, url:string}>('photolist', ref => {
      return ref.orderBy('createdAt', 'desc');
    }).snapshotChanges()
              .pipe(map(actions => actions.map(action => {
                const data = action.payload.doc.data();
                return {createdAt: data.createdAt, name: data.name, url: data.url};
              })));
  }
}

现在可以从数据库中获取照片信息了。让我们在photo-list组件中实现照片列表功能吧。

import { Component, OnInit } from '@angular/core';
import { NetworkService } from '../shared/service/network.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-photo-list',
  templateUrl: './photo-list.page.html',
  styleUrls: ['./photo-list.page.scss'],
})
export class PhotoListPage implements OnInit {
  public photolist: Observable<{createdAt: Date, name: string, url:string}[]>;

  constructor(
    private network: NetworkService
  ) { }

  ngOnInit() {
    this.photolist = this.network.getPhotos();
  }

}

这里我们创建了一个名为photolist的变量,并将照片信息的列表存储在其中。如果在服务器端添加、删除或更改数据,它会自动接收这些更改(这就是firebase的优点之一)。我们也要对屏幕显示的部分进行修改。

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>photo-list</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-grid>
    <ion-row *ngFor="let photo of photolist | async">
      <ion-col>
        <ion-img [src]="photo.url"></ion-img>
      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>

关于这一点,我想要进行一点说明,有两个方面。

ngFor是什么?

如果对Angular不熟悉的话,可能会感到疑惑。”*ngFor”是用于在HTML上进行循环处理的语法。包含”*ngFor”的标签成为循环的单位。更多详细信息请参考官方文档。

这次使用它是为了显示从服务器获取的照片信息列表。

async究竟是什么?

这也是Angular特有的东西。它被称为pipe,它是一种在显示在屏幕上之前插入的处理方法,有各种各样的方法,并且您还可以自己创建。

“async”是用于在异步处理中显示获取的值的工具。请参考官方文档以获取更详细的内容。
https://angular.jp/guide/observables-in-angular#%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%83%91%E3%82%A4%E3%83%97
简言之,它是为了能够显示延迟获取的数据的处理方法。

确认动作

让我们打开照片列表页面,确认是否能够获取到照片。

image.png

如果照片以这样的方式显示,那就好了。

广告
将在 10 秒后关闭
bannerAds