Angular+Firebase+Algoliaで全文検索機能搭載方法

#Angular+Firebase+Algoliaでサイト内全文検索機能搭載方法

全文検索。どうやらFirestoreならAlgoliaというバックエンドサービスを利用するのがデフォのようだ。グーグルの[公式ドキュメント](https://firebase.google.com/docs/firestore/solutions/search)にも「Solution: Algolia」と。ドキュメントが少なく有意なサイトは有料だったりとかなり苦労したので備忘録。
<strong>※ FirestoreではSQLの'LIKE'句のようなものがないのよ～</strong>

###Prerequisites

- 環境

```cmd
Angular CLI: 7.1.4
Node: 10.15.3
OS: win32 x64
Angular: 7.1.4
```
- コードはTypescript

- すでにFirestoreを利用した動的サイトがある（オンラインフォーラムのようなもの）


###導入の仕方
####Algolia設定
#####Algoliaにアカウントを登録する。https://www.algolia.com
#####Indexを作成する。
ダッシュボードの左メニューから「indeices」を選択 → 「Create Index」→ Index名は任意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/db5bdd33-4bb3-75f5-d4bb-5929dfb52ce3.png)

インデックスが作成された。まだカラのままでよい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/7a227d0c-e112-6d9b-a01c-ace6641ec518.png)


#####AlgoliaのAPIキーを取得
ダッシュボードの左メニューの「API Keys」にすべて表示されている。使用するのはいかのとおり。

- Application ID
- Admin API Key
- Search-Only API Key
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/865f580d-72dc-978d-14eb-df29bbcf327b.png)

####angular-instantsearchをインストール・設定方法
#####インストール方法
以下のコマンドでパッケージをインストールして、

```cmd
C:\projectroot\npm install angular-instantsearch --save
```
app.module.tsファイルに以下を追記

```typescript
import { NgAisModule } from 'angular-instantsearch';

@NgModule({
  declarations: [...],
  imports: [NgAisModule.forRoot(), ...], // .forRoot()をくっつける
  bootstrap: [...]
})
```

angular.jsonファイルに以下を追記

```json
{
  "styles": [
    "node_modules/angular-instantsearch/bundles/instantsearch.min.css",
    "node_modules/angular-instantsearch/bundles/instantsearch-theme-algolia.min.css"
  ]
}
```
polyfills.tsファイルの末尾に以下を追記

```typescript
(window as any).process = {
  env: { DEBUG: undefined },
};
```

<br>
####デモってみる？
どんな感じに使えるのか試すことができる。
##### 適当なテスト用コンポーネントを作成

```cmd
C:\projectroot\ng g c component/search-ui
```
app.route.tsファイルに以下追記

```typescript
import { SearchUiComponent } from './component/search-ui/search-ui.component';


export const AppRoutes: Routes = [
    ・・・
    {path: "algoliasearch", component: SearchUiComponent}
];
```

#####コードを張り付けてみる
search-ui.component.htmlに以下のコードを。


```html
<h1>My first search app</h1>
<ais-instantsearch
  [config]="{
    apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
    appId: 'latency',
    indexName: 'instant_search',
    routing: true
  }"
>
  <ais-hits></ais-hits>
</ais-instantsearch>

```
ここで使っているapikey、appId、indexNameはデモ用。そのまんま使う。`ng searve -o`で起動して、http://localhost:4200/algoliasearch
をブラウズしてみると画面はこのようにデータの一覧が↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/8b92530e-303d-720a-dfb3-d0fb049b1fb2.png)

`<ais-hits></ais-hits>`の部分が一覧を表示している。

#####リザルト表示のカスタマイズ
`<ais-hits></ais-hits>`の部分を以下のようにする


```html
<ais-hits>
  <ng-template let-hits="hits">
    <div *ngFor="let hit of hits">
      Hit {{hit.objectID}}:
      <ais-highlight attribute="name" [hit]="hit">
      </ais-highlight>
    </div>
  </ng-template>
</ais-hits>
```

#####サーチボックスをつくる
このタグを挿入→ `<ais-search-box></ais-search-box>`

コード全体


```html
<h1>My first search app</h1>
<ais-instantsearch
  [config]="{
    apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
    appId: 'latency',
    indexName: 'instant_search',
    routing: true
  }"
>
<ais-search-box></ais-search-box>
<ais-hits>
  <ng-template let-hits="hits">
    <div *ngFor="let hit of hits">
      Hit {{hit.objectID}}:
      <ais-highlight attribute="name" [hit]="hit">
      </ais-highlight>
    </div>
  </ng-template>
</ais-hits>
</ais-instantsearch>
```

こうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/75182c8c-7dcf-420b-8bd2-70463074b250.png)

search-ui.component.tsはいっさいいじっていない。

#####検索してみる
適当に検索してみる。リアルタイム検索になってまあまあ感動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/cf5551c4-b48a-8c37-1709-e645e27443ba.png)


apikey、appId、indexNameを自分用のに変更するだけで同じようになるのだな、とわかる。これはかなり簡単そう！


###Firestoreデータのインポート

既存のデータをFirestoreからAlgoliaへJSON形式でエクスポートインポートしなければならない。

####nodeスクリプトでやる方法
すでにあるFirestoreのデータをAlgoliaにバルクインポートする。
別プロジェクトフォルダを作成し、npm initして各モジュールをインストール

```cmd
$ npm install algoliasearch --save
$ npm install dotenv --save
$ npm install firebase --save
```
.envファイルを作成して以下のように（Admin API Keyは絶対に漏らさないように！）

```typescript
ALGOLIA_APP_ID=<algolia-app-id>
ALGOLIA_API_KEY=<algolia-api-key> // Admin API Keyのこと
ALGOLIA_INDEX_NAME='＜Algoliaインデックス名＞'
FIREBASE_DATABASE_URL=https://<my-firebase-database>.firebaseio.com
FIREBASE_PROJECT_ID='＜FirebaseプロジェクトID＞'
FIRESTORE_COLLECTION_NAME='＜コレクション名＞'
```
index.jsを作成して、以下のコードをペースト

```typescript
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const firebase = require('firebase');
const firestore = require('firebase/firestore');

// load values from the .env file in this directory into process.env
dotenv.config();

// initializes the firebase database.
firebase.initializeApp({
    projectId: process.env.FIREBASE_PROJECT_ID,
    databaseURL: process.env.FIREBASE_DATABASE_URL
  })
const db = firebase.firestore();

// configure algolia
const algolia = algoliasearch(
  process.env.ALGOLIA_APP_ID,
  process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);

// Get all contacts from Firebase
let docRef = db.collection(process.env.FIRESTORE_COLLECTION_NAME);
const records = [];
docRef.get()
    .then((snapshot) => {
        snapshot.forEach((doc) => {
            // get the key and data from the snapshot
            const childKey = doc.id;
            const childData = doc.data();
            // We set the Algolia objectID as the Firebase .key
            childData.objectID = childKey;
            // Add object for indexing
            records.push(childData);
            console.log(doc.id, '=>', doc.data());
        });
        // Add or update new objects
        index.saveObjects(records).then(() => {
            console.log('Documents imported into Algolia');
            process.exit(0);
        })
        .catch(error => {
            console.error('Error when importing documents into Algolia', error);
            process.exit(1);
        });
    })
    .catch((err) => {
        console.error('Error getting documents', error);
    });
```


したら、node index.jsコマンドで実行。

これでうまくいくはずだが、、よくわからないがAlgoliaインデックスにデータを放り込みに行くところでエラー。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/798c93dd-f015-4431-820b-41fba83cae3e.png)
```    method: 'POST',
       timeouts: [Object],
       url: '/1/indexes/prod_commentsfaqs/batch',
       statusCode: 400 } ],
  statusCode: 400 }
```

？？？
調査してトラブルシュートするより以下の代替方法が早いと思った。
####手動でやる方法

まずAngularで適当なテスト用コンポーネントを作成。先ほど作ったテスト用search-uiを使うのでもなんでもいい。
search-ui.component.tsファイルに以下のコードを書いてFirestoreのコレクションからデータを引っ張りJSONで表示。

```typescript
//search-ui.component.ts
import { Component, OnInit} from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Post } from '../../class/post';

@Component({
  selector: 'app-search-ui',
  templateUrl: './search-ui.component.html',
  styleUrls: ['./search-ui.component.css']
})
export class SearchUiComponent implements OnInit {
  posts: Post[];
  postsCollection: AngularFirestoreCollection<Post>;
  postsJSON: string;

  constructor(private db: AngularFirestore) {}

  ngOnInit() {
    this.getPosts();
  }

  getPosts() {
    this.postsCollection = this.db.collection('posts');
    this.postsCollection.valueChanges().subscribe(data => { 
     this.posts = data;
     const exportJSON = JSON.stringify(this.posts);
      this.postsJSON = exportJSON;
    });
  }

}
```
HTMLのほうは以下のように。

```html
<!--search-ui.component.html-->
<h2>FirestoreのPostコレクションのエクスポート</h2>
{{postsJSON}}
```

`http://localhost:4200/algoliasearch`のページにデータがJSON表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/41806f50-347f-41f6-21ac-7206a25e1dd5.png)

このままだと汚いので[JSONきれい ～JSON整形ツール～](https://tools.m-bsys.com/development_tooles/json-beautifier.php)で整える（別にやらなくてもいいけど）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/2dfef51b-3d07-aa56-2840-8bb9bb6e5d4f.png)


綺麗になったJSONをNotepad++などに張り付け、"id"を"objectID"に全置換する。それをJSONファイルとして保存。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/ac1fdb5b-66ca-0819-2f07-43fa67d01436.png)


AlgoliaコンソールでJSONファイルを手動でアップロード。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/7e2c8c6f-99bf-9903-f186-5d3b84af86e2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/c849ed4e-2bba-9d59-25f1-ba2c1412de8c.png)


完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/bdc99872-4104-e83d-a252-b1edd5f29a28.png)
失敗したスクリプトをいじって直接JSONをローカルに吐き出させてもいいと思うし好きなやり方で。

2百件ほどのデータがあったのでこんな感じに2百件分のインデックスが登録された（コンソール1ページに10件表示されている）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/0ae9f813-e8a4-4e0d-5bb2-594e3f817ed7.png)

###ライブコードの変更とアトリビュート追加
ではデモで使ったコードをそのまま利用して本番アプリで確認。やることはデモ用APIキーを自分用のものに変更し、アトリビュートを変更する。

####コードを変更
HTMLにあったデモ用コード。Alogliaキーを自分用に変えるだけ。`[config]="{...}"`のところ。べた書きでもいいけど、Angularのenvironment/environment.tsを使おうか。

＜デモ用コード＞

```typescript
//search-ui.component.html
<h1>My first search app</h1>
<ais-instantsearch
  [config]="{
    apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
    appId: 'latency',
    indexName: 'instant_search',
    routing: true
  }"
>
<ais-search-box></ais-search-box>
<ais-hits>
  <ng-template let-hits="hits">
    <div *ngFor="let hit of hits">
      Hit {{hit.objectID}}:
      <ais-highlight attribute="message" [hit]="hit">
      </ais-highlight>
    </div>
  </ng-template>
</ais-hits>
</ais-instantsearch>
```

…では、environment.tsとenvironment.prod.tsに以下のように`algolia:{...}`の部分を追記。。

```typescript
//environment.tsとenvironment.prod.ts
export const environment = {
  production: false,
  firebase : {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: ""
  },
  algolia: {            // <==追記
    appId: '＜App ID＞',
    apiKey: '＜Admin Key＞',
    indexName: '＜インデックス名＞'
  }
};
```


search-ui.component.tsに以下を追記。

```typescript
import { environment } from 'src/environments/environment'; //<==追記

export class SearchUiComponent implements OnInit {

  searchConfig = {              //<==追記
    ...environment.algolia
  };

…以下略

```

したらHTMLの方はAloglia keyの部分をこう省略できる。`[config]="searchConfig"`
それからデモではattribute="name"となってるがわたしがインポートしたデータは属性nameのレコードはない。userNameとmessageがあるのでそれに変更する。（見やすくするため`<hr>`も入れた）

```html
<!--search-ui.component.html-->
<h1>My first search app</h1>
<ais-instantsearch [config]="searchConfig">
<ais-search-box></ais-search-box>
<ais-hits>
  <ng-template let-hits="hits">
    <div *ngFor="let hit of hits">
　　  <hr>
      Hit {{hit.objectID}}:
      <ais-highlight attribute="userName" [hit]="hit"></ais-highlight>
      <ais-highlight attribute="message" [hit]="hit"></ais-highlight>
　　   <hr>
      </ais-highlight>
    </div>
  </ng-template>
</ais-hits>
</ais-instantsearch>
```

そうするとこんな感じだ。
- 初期ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/49646748-0d0e-fb07-2d3f-f28261dc0599.png)
- 検索すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/0039c0c8-4e38-cdb9-6b3c-cbe4a5a1809d.png)

###カスタマイズ
いろいろあるので少しだけ。
最初の初期ページでなにも表示させておきたくない場合、booleanフラグを設定すればよいのではないか。HTMLの`<ais-search-box>`タグには`(change)="関数名($event)"`を追記できる。関数はもちろん.tsファイルに書く。例えば、

```html
<!--search-ui.component.html-->
<ais-instantsearch [config]="searchConfig">
<ais-search-box placeholder="フォーラム内検索" (change)="searchChanged($event)"></ais-search-box>
<ais-hits *ngIf="showResults">
  <ng-template let-hits="hits">
    <div *ngFor="let hit of hits">
      <hr>
      Hit {{hit.objectID}}:
      <ais-highlight attribute="name" [hit]="hit"></ais-highlight>
      <ais-highlight attribute="message" [hit]="hit"></ais-highlight>
      <hr>
    </div>
  </ng-template>
</ais-hits>
</ais-instantsearch>
```

```typescript
//search-ui.component.ts
import { Component, OnInit } from '@angular/core';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-search-ui',
  templateUrl: './search-ui.component.html',
  styleUrls: ['./search-ui.component.css']
})
export class SearchUiComponent implements OnInit {

  searchConfig = {
    ...environment.algolia
  };

  showResults: boolean = false; //<==フラッグ

  constructor() {}

  ngOnInit() {
  }

  async searchChanged(query) { //<==関数
    if (query.length) {
     this.showResults = true;
    } else {
     this.showResults = false;
    }
  }
}
```

showResultsがTrueの場合のみ表示するようにする。AngularのngIfを使って`<ais-hits *ngIf="showResults">`とすればshowResultsの初期値はfalseなのでなにも表示されない。
showResultsがTrueになるには検索ボックスになにか文字入力しなければならない。

`placeholder=`でプレイスホルダーを設定できる。

あとはいろいろCSSで見栄えをば。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/6eaf299d-be63-f16d-ddd6-bb5fdb8ab3fe.png)



###Firebase Functionの設定
既存データはAlgoliaに載せれたので、Firestoreデータの追加や削除にシンクロさせる。Firebase Functionを使う。

#####事前準備
まずFirebaseプロジェクトを無料のSparkプランから従量課金制のBlazeプランへ変更する。

#####利用パッケージのインストール

```cmd
C:\projectroot>npm install firebase-functions@latest firebase-admin@latest --save
C:\projectroot>npm install -g firebase-tools
```

##### firebase initを実行

まずログイン

```cmd
C:\projectroot>firebase login
```

```cmd
Already logged in as xxxxxxx@gmail.com


   ╭───────────────────────────────────────────╮
   │                                           │
   │      Update available 6.5.0 → 6.5.3       │
   │   Run npm i -g firebase-tools to update   │
   │                                           │
   ╰───────────────────────────────────────────╯
```



<br>
ではfirebase initコマンドをば、

```cmd
C:\projectroot>firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  C:\projectroot

? Are you ready to proceed? (Y/n)
```



Yといれてエンターで次のように

```cmd
? Are you ready to proceed? Yes
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choi
ces. (Press <space> to select)
>( ) Database: Deploy Firebase Realtime Database Rules
 ( ) Firestore: Deploy rules and create indexes for Firestore
 ( ) Functions: Configure and deploy Cloud Functions
 ( ) Hosting: Configure and deploy Firebase Hosting sites
 ( ) Storage: Deploy Cloud Storage security rules
```





Functionsを選ぶ。Angularで作成したWebサイトをHostingにアップするならHostingも選んでおく。

```cmd
 ( ) Database: Deploy Firebase Realtime Database Rules
 ( ) Firestore: Deploy rules and create indexes for Firestore
 (*) Functions: Configure and deploy Cloud Functions
>(*) Hosting: Configure and deploy Firebase Hosting sites
 ( ) Storage: Deploy Cloud Storage security rules

```



Firebase上に存在するどのプロジェクトと紐づけるのか聞いてくるので該当プロジェクトを選択。

```cmd
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Select a default Firebase project for this directory:
  [don't setup a default project]
  codegrid-practice (CodeGrid-practice)
> project-xxxx (Projecthogehoge)
  student-app-xxxx (student-app)
  [create a new project]
```


<br>
JavaScriptとTypeScriptのどっちか選べと。TypeScriptに。TypeScriptの場合のドキュメントが少ないので苦労のひとつとなる。

```cmd
=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions?
  JavaScript
> TypeScript
```



最後の２つの質問はよく意味わからない。どっちもYesで。

```cmd
? Do you want to use TSLint to catch probable bugs and enforce style? Yes
+  Wrote functions/package.json
+  Wrote functions/tslint.json
+  Wrote functions/tsconfig.json
+  Wrote functions/src/index.ts
+  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes
```


<br>
おっと、Hostingの設定にも行ってしまった。
Angularを使ってるのでもちろん下記のように答える。`dist`, `dist/index.html`

```cmd
=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
+  Wrote dist/index.html
```

functionsフォルダができる。この配下のindex.tsファイルにFunctionとなる関数を書いていくことになる。複数作りたかったらindex.tsに複数書いていく（index.tsにexport hogehoge {…処理…} がいくつも存在することになる）
![1557567299943.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/94d327b4-fe1d-e620-b7d6-41784e0de24e.png)


### functionsのスクリプトをデプロイ

まずそのfunctionsフォルダにcdで移ってから、次のコマンドでnpmパッケージをいろいろインストールする。
4つ目のalgoliasearchgがAlgolia用のもの。

```cmd
C:\projectroot>cd functions
C:\projectroot\functions>npm install --save request
C:\projectroot\functions>npm install @types/request-promise
C:\projectroot\functions>npm install request-promise --save

C:\projectroot\functions>npm install algoliasearch --save

```


#####Algolia APIキーの環境変数設定
コードにベタ書きでもいいけど以下のコマンドで環境変数を設定できる。

```cmd
C:\projectroot>firebase functions:config:set algolia.app_id=<Application ID>
C:\projectroot>firebase functions:config:set algolia.api_key=<Admin API Key>
```

####Functionsの作成

- まずfunctionsフォルダの`tsconfig.json`に以下を追記する。
`"suppressImplicitAnyIndexErrors": true`

```json
{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "suppressImplicitAnyIndexErrors": true
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}
```

- index.tsのコードは以下の通り。

```typescript
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

// firebase の初期化
admin.initializeApp(functions.config().firebase) 

// algoliaのキー関連
const algoliasearch = require('algoliasearch')
const ALGOLIA_ID = functions.config().algolia.app_id
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key
const algolia = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);

const index = algolia.initIndex('＜Algoliaインデックス名＞');

// algolia全文検索API利用関数// コレクションに新規レコードが追加されると起動
exports.onPostCreated = functions.firestore.document('posts/{id}').onCreate((snap, context) => {
  // 新規レコードの情報をIndex用オブジェクトに格納
  const objectID = snap.id
  const data = snap.data();
    // AlgoliaへIndex
  return index.addObject({
    objectID,
    ...data
  })
})

// algolia全文検索API利用関数// コレクションのレコードが削除されると起動
exports.onPostDeleted = functions.firestore.document('posts/{id}').onDelete((snap, context) => {
  // Index用オブジェクトを削除
  index.deleteObject(snap.id)
})

// algolia全文検索API利用関数// コレクションのレコードが更新されると起動
exports.onPostUpdated = functions.firestore.document('posts/{id}').onUpdate((change, context) => {
  // 更新レコードの情報をIndex用オブジェクトに格納
  const objectID = change.after.id
  const newData = change.after.data();
    // AlgoliaへIndex
  return index.saveObject({
    objectID,
    ...newData
  })
})
```

Functionは追加・削除・変更用の３つ。

[Firebase公式ドキュメント「全文検索」](https://firebase.google.com/docs/firestore/solutions/search?hl=ja)はJavaScriptの表記の場合なので気を付けて。

- 以下のコマンドでfunctionをアップロード

```cmd
C:\projectroot>firebase deploy --only functions
```

- Firebase Functionsに関数できた。３つあるのがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/047f9686-d4b4-3803-76a8-58cf9cd782d7.png)

<br>

これでFirestoreのコレクションにデータが追加・削除・変更されると自動的にAlgoliaインデックスも追加・削除・変更されることになる。

以上。




<br>
参考：
[Welcome to Angular InstantSearch](https://community.algolia.com/angular-instantsearch/getting-started.html)
[Algolia Fulltext Search Cloud Functions](https://fireship.io/lessons/algolia-cloud-functions/)
[Angular Full Text Search With Algolia Backend - Part 2](https://angularfirebase.com/lessons/angular-full-text-search-with-algolia-backend-part-2/#Cloud-Function-Indexing-a-Document)
[firebaseとAlgoliaを連携させて全文検索を導入する手順メモ](https://qrunch.net/@oSsAl6sFdZnihjYb/entries/ldKT8e2KxwlUDnDz)