LoginSignup
2

More than 5 years have passed since last update.

algolia のフロント側を Vue.js + TypeScript で試す。無限スクロールしたい...

Last updated at Posted at 2019-02-11

はじめに

Algolia で検索しながら無限スクロールがしたかったので、algolia に Vue.js + TypeScript で入門します。最終的に以下の gif のようなものができます。

algolia.gif

それとなくできたのですが、細かい部分が意図した動きにならず困っています。が、細かい部分なので今は無視しています。
公式サンプルはこちら infinite scroll なのですが、これも意図した動きをしてくれません。

何が困っているかというと、一度スクロールをすると data の page が this.page++; によって大きな数になるのですが、それ以降の検索でうまいことリセットされず、意図した検索結果が返ってこないのです。(最初の20件とかしか返さなくなる..)

僕の場合はこの困りごとを、以下のような方法でお茶を濁しています。これも完璧ではないのですが。

e.addEventListener("input", () => {
  this.page = 1 // まずはリセットして
  this.resetPage() // さらに足りなければ +1 するようの独自メソッド
})  

とはいえある程度うごくものなので、まとめておきます。

Step1. Vue CLI でアプリをつくります

まずはおもむろに、以下コマンドです。TypeScrip を選びましたので、以降 TS です。

$ vue create study-algolia
$ yarn serve

これで、めでたく無事エラーがでます。前は出なかったのになー。
https://github.com/webpack/webpack/issues/8768#issuecomment-462090153

仕方ないので、言われるままに以下

image.png

Step2. ふつうに algolia をつかってみる

vue でフロントを書くときに便利なツールとして vue-instantsearch が用意されていますので、これを使います。

$ yarn add vue-instantsearch

main.ts に以下を追記します。

main.ts
+ import InstantSearch from 'vue-instantsearch';
+ Vue.use(InstantSearch);

これで、 yarn serve すると以下のように怒られます。

error
Could not find a declaration file for module 'vue-instantsearch'.

Vue-instantsearch.d.ts を書きます。これで、解消します。

src/vue-instantsearch.t.ds
declare module 'vue-instantsearch'

App.vue を以下のようにすることでひとまず動きます。
cf. Create your first search experience

App.vue
<template>
  <ais-index
    app-id="latency"
    api-key="3d9875e51fbd20c7754e65422f7ce5e1"
    index-name="bestbuy"
  >
    <ais-search-box></ais-search-box>
    <ais-results>
      <template slot-scope="{ result }">
        <h2>
          <ais-highlight :result="result" attribute-name="name"></ais-highlight>
        </h2>
      </template>
    </ais-results>
  </ais-index>
</template>

こんな感じ。

image.png

ここまでで、ふつうに algolia を使うところまで出来ました。

Step3. 自分が用意したデータでインデックスをつくってみる

動作確認をしたいので、index データを自分でつくってみます。go で書きます。

main.go
import (
    "encoding/json"
    "github.com/algolia/algoliasearch-client-go/algoliasearch"
    "net/http"
)

func main() {
    client := algoliasearch.NewClient(
        "{Your Application ID}",
        "{Admin API Key}",
    )
    index := client.InitIndex("test_notes")
    str := `[{"id": 1, "content": "Hello world"}, {"id": 2, "content": "I like Vue.js"}]`
    var posts []algoliasearch.Object
    d := json.NewDecoder(strings.NewReader(str))
    d.Decode(&posts)
    index.AddObjects(posts)
}
$ go run main.go

これで、インデックスがつくられてダッシュボードからも確認が可能になります。

image.png

これを Vue.js 側から呼び出すには以下の部分を修正します。

App.vue
// 抜粋
<div id="app" class="container-fluid">
    <ais-index
      id="main"
      app-id="自分の" // <- ここ
      api-key="自分の" // <- ここ
      index-name="test_notes" // <- ここ
// 抜粋
    <ais-highlight :result="result" attribute-name="content"></ais-highlight> // <- ここ

image.png

良い感じに呼び出せています。

大量にデータを入れておきたいので、json ファイルから index を作れるように go のコードを変更しておきます。

main.go
func main() {
    client := algoliasearch.NewClient(
        "{Your Application ID}",
        "{Admin API Key}",
    )
    index := client.InitIndex("test_notes")
    jsonFile, _ := os.Open("notes.json")

    var posts []algoliasearch.Object
    byteValue, _ := ioutil.ReadAll(jsonFile)
    json.Unmarshal([]byte(byteValue), &posts)
    index.AddObjects(posts)
}

notes.json に json データを大量に書いて置いてください。無限スクロールが楽しめるように。

Step4. 無限スクロールに対応する

cf. Infinite scroll

$ yarn add vue-observe-visibility
main.ts
+ import VueObserveVisibility from 'vue-observe-visibility'
+ Vue.use(VueObserveVisibility)
App.vue
     index-name="test_notes"
+    :query-parameters="{'page': page}"
   >
     <ais-search-box></ais-search-box>
-    <ais-results>
+    <ais-results :stack="true">
       <template slot-scope="{ result }">
         <h2>
           <ais-highlight :result="result" attribute-name="content"></ais-highlight>
         </h2>
       </template>
     </ais-results>
+    <div v-observe-visibility="loadMore">Loading more...</div>
   </ais-index>
 </template>

以上で、無限スクロールに対応できました。

Step5. 検索のたびに page = 1 にリセットしてみる

調整をした App.vue の全体を記載しておきます。 mountedresetPage を追加しています。

これで冒頭の gif が得られます。

main.ts
import Vue from 'vue'
import App from './App.vue'
import InstantSearch from 'vue-instantsearch';
import VueObserveVisibility from 'vue-observe-visibility'

Vue.use(InstantSearch);
Vue.use(VueObserveVisibility)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
App.vue
<template>
  <div id="app" class="container-fluid">
    <ais-index
      id="main"
      app-id="自分の"
      api-key="自分の"
      index-name="test_notes"
      :query-parameters="{'page': page}"
    >
      <div class="row">
        <ais-search-box>
          <div class="input-group">
            <ais-input
              id="input"
              placeholder="Search product by name or reference..."
              :class-names="{
                'ais-input': 'form-control'
                }"
            ></ais-input>
          </div>
        </ais-search-box>
      </div>
      <div class="row">
        <ais-results :stack="true">
          <template scope="{ result }">
            <div class="content">
              <ais-highlight :result="result" attribute-name="content"></ais-highlight>
            </div>
          </template>
        </ais-results>
        <ais-no-results></ais-no-results>
        <div id="loadMore" v-observe-visibility="loadMore">Loading more...</div>
      </div>
    </ais-index>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class App extends Vue {
  page = 1;
  loadMore(isVisible: any) {
    if (isVisible) {
      this.page++;
    }
  }

  mounted() {
    const i = document.querySelector("#input");
    i!.addEventListener("input", () => {
      this.page = 1;
      this.resetPage();
    });
  }

  // page = 1 にして 20 件を表示するが、
  // それでも画面に div id="loadMore" が表示されている場合は page++ するため。
  // ここのロジックが中途半端...
  resetPage() {
    const e = document.querySelector("#loadMore");
    const topPosition = e!.getBoundingClientRect().top;
    const windowHeight = window.innerHeight;
    if (topPosition <= windowHeight) {
      this.page++;
    }
  }
}
</script>

<style>
#app {
  text-align: center;
}
#input {
  margin: 20px;
}
.content {
  height: 40px;
}
</style>

以上です。
algolia に入門してみましたが、とても使いやすい気がします。細かなところは、もっと深い部分をちゃんと読まないと。🍋

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2