【RailsAPI+Vue.js】Pagy를 이용한 페이지네이션 구현

소개



RailsAPI와 Vuetify로 페이지 네이션을 만들었습니다.
gem을 어느 쪽으로 할까 조사해 보았는데, Pagy가 부드럽게 심플! 가벼운! 라고 하는 것 같기 때문에, Pagy를 사용했습니다.

환경, 사용 기술


  • Rails 5.2.4.2
  • Pagy 3.8.1
  • Vue.js 4.3.1
  • Vuetify 2.2.21
  • axios 0.19.2

  • Vuetify는 다른 것들도 대체 가능할 것이라고 생각합니다.

    Rails 측



    Pagy 초기 설정



    How To | Pagy 에 쓰여진 거리입니다.

    Gemfile
    gem 'pagy', '~> 3.5'
    

    매번 익숙한 $ bundle install를 실행하고 config/initializers/pagy.rb에 설정 파일을 만듭니다.
    템플릿 을 복사하여 필요한 곳만 주석 처리를 제거합니다.

    config/initializers/pagy.rb
    Pagy::VARS[:items] = 3  # 1ページに3件取得する
    

    컨트롤러



    app/controllers/api/v1/tweets_controller.rb
    class Api::V1::UsersController < Api::V1::BaseController
    + include Pagy::Backend
    
      def index
    -   users = User.all
    +   pagy, users = pagy(User.all)
        render json: users
      end
    end
    

    Postman에서 API를 두드려 응답을 확인해 보겠습니다.

    이와 같이, user의 데이터가 3건씩 취득되고 있었습니다(시리얼라이저를 사용하고 있으므로, 컬럼명이 캬멜 케이스가 되고 있습니다).

    그러나 이것만으로는 현재 페이지와 총 페이지 수를 알 수 없습니다. 프론트측의 페이지네이션 컴퍼넌트에서는 그러한 데이터가 필요하므로, 추가로 기술해 갑니다.


    헤더에 페이지 정보 넣기



    app/controllers/api/v1/tweets_controller.rb
    + require 'pagy/extras/headers'
    
    class Api::V1::UsersController < Api::V1::BaseController
      include Pagy::Backend
    
      def index
        pagy, users = pagy(User.all)
    +   pagy_headers_merge(pagy)
        render json: users
      end
    end
    

    인용구 : Headers | Pagy

    이 설명은 응답 헤더에 다음 정보를 저장합니다.



    KEY


    링크

    Current-Page

    Page-Items

    Total-Pages

    Total-Count


    "Link"의 내용(실제는 일행)↓
    <http://127.0.0.1:3000/api/v1/users?page=1>; rel="first",
    <http://127.0.0.1:3000/api/v1/users?page=1>; rel="prev",
    <http://127.0.0.1:3000/api/v1/users?page=3>; rel="next",
    <http://127.0.0.1:3000/api/v1/users?page=3>; rel="last"
    

    이것으로 Rails 측의 처리는 끝입니다.
    공통화하는 경우는 after_action 를 사용하는 방법도 있습니다 (see 공식).

    Vue측



    Vue-router는 사용하지 않습니다.

    템플릿 부분



    Pagination component — Vuetify.js 을 조금 사용자 정의합니다.
    <template>
      <div class="text-center">
        <v-pagination
          v-model="currentPage"
          :length="page.totalPages"
        ></v-pagination>
      </div>
    </template>
    <script>
      export default {
        data () {
          return {
            requestUrl: "/api/v1/users",
            page: {
              currentPage: 1,
              totalPages: 5,
            }
          }
        },
      }
    </script>
    


    이제 일단 페이지 네이션을 볼 수는 있었지만 여전히 버튼을 눌러도 page.currentPage의 값이 변경됩니다.

    버튼을 누를 때의 동작



    컴퍼넌트로부터 @input 이벤트를 받아, changePage 메소드로 처리를 실시합니다.
    <template>
      <div class="text-center">
        <v-pagination
          v-model="currentPage"
          :length="page.totalPages"
    +     @input="changePage"
        ></v-pagination>
      </div>
    </template>
    <script>
    export default {
      data () {
        return {
    +     requestUrl: "/api/v1/users",
          page: {
            currentPage: 1,
            totalPages: 5,
          }
        }
      },
    + methods: {
    +   changePage(val) {
    +     // 処理
    +   }
    + }
    }
    </script>
    
    methods: {
      async changePage(val) {
        // "/api/v1/users?page=2"などにGETリクエストを送る
        const response = await this.$axios.get(`${this.requestUrl}?page=${val}`)
        // 受け取ったusersデータを格納する
        const { users } = response.data
        this.users = users
      }
    }
    

    페이지 로딩시의 데이터 취득


    mounted 에서 첫 번째 화면을 그릴 때의 움직임을 설명합니다.
    async mounted() {
      try {
        // "/api/v1/users"にGETリクエストを送る
        const response = await this.$axios.get(this.requestUrl)
        // それぞれのdataにレスポンスの値を代入する
        this.page.totalPages = Number(response.headers["total-pages"])
        const { users } = response.data
        this.users = users
      }
    }
    

    최종 코드


    <template>
      <!-- usersの表示部分。省略 -->
      <div class="text-center">
        <v-pagination
          v-model="page.currentPage"
          :length="page.totalPages"
          @input="changePage"
        />
      </div>
    </template>
    
    <script>
    import goTo from "vuetify/es5/services/goto"  // しれっと追加している
    export default {
      data() {
        return {
          requestUrl: "/api/v1/users",
          page: {
            currentPage: 1,
            totalPages: 1,
          },
          users: []
        }
      },
      async mounted() {
        try {
          const response = await this.$axios.get(this.requestUrl)
          this.page.totalPages = Number(response.headers["total-pages"])
          const { users } = response.data
          this.users = users
        }
      },
      methods: {
        async changePage(val) {
          goTo(0)  // ページ最上部までスクロール。Vuetifyのメソッド
          const res = await this.$axios.get(`${this.requestUrl}?page=${val}`)
          const { users } = res.data
          this.users = users
        }
      }
    }
    </script>
    

    그건 그렇고



    추가로 헤더에 정보를 전달하는 경우



    다음과 같이 작성하여 추가할 수 있습니다. requestUrl 를 초기치의 data로 설정하는 것이 어려운 경우는, 이와 같이 헤더에 건네주어 받는 방법도 있습니다.
      def index
        pagy, users = pagy(User.all)
        pagy_headers_merge(pagy)
        response.headers.merge!({ 'Request-Url' => request.path_info })
        render json: users
      end
    



    참고 링크



    rails API로 페이지 네이션 구현
    【vue.js】 Vuetify로 간단 페이지 네이션(Paginations)

    좋은 웹페이지 즐겨찾기