使用 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”。然后您会看到这样的界面。
请点击开始指南。
请点击“确认”。
如果还保持现状,未经认证的用户将无法上传,因此我想要改变规则。请打开规则选项卡,将规则更改如下。
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
现在,可以准备将照片上传到服务器了。
上传照片
让我们在应用程序中添加上传照片的处理功能。目前为止,我认为已经可以选择照片并显示预览。
让我们从这里开始,按下发送按钮以上传图片。直观地,我们可能会想要在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控制台,可以看到
可以看到照片已经被上传了。这样就完成了照片上传功能。
将上传的照片信息保存到数据库中
为了使上传的照片能够在应用程序中使用,需要创建一个保存照片信息的数据库,并将信息存储在其中。首先,我们先进行一些配置来使用Firebase数据库。
在Firebase控制台中,点击”数据库”按钮。
请点击”创建数据库”。
请点击选择“开始测试模式”,然后点击“启用”。
现在可以使用数据库了。
导入数据库模块
返回应用程序,并导入适用于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);
});
});
}
}
好的,我们用这个试试看。现在让我们尝试一下当上传照片时,会不会被写入到数据库中。
好的,已经正确地写入了。这样,我们也可以将数据写入数据库了。
读取数据库
最后,我想要能够将上传的照片以列表形式获取。虽然我认为你已经大致理解了流程,但正是如此,我将在服务中添加这个功能。
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
简言之,它是为了能够显示延迟获取的数据的处理方法。
确认动作
让我们打开照片列表页面,确认是否能够获取到照片。
如果照片以这样的方式显示,那就好了。