2024년 10월 29일

코딩 - Vue3 : AG-Grid 커뮤니티 버전 기반의 페이징 그리드 컴포넌트 만들기

 AG Grid Community 버전은 매우 인기 있는 오픈소스 그리드 컴포넌트로, 다양한 기능을 제공하지만 상업적인 용도로 사용할 때는 프로 버전으로의 업그레이드가 필요할 수도 있다. 커뮤니티 버전의 이점과 한계를 살펴보면 다음과 같다.

AG Grid Community  버전의 이점

  1. 무료 사용: 커뮤니티 버전은 오픈소스로 제공되므로 무료로 사용할 수 있다. 중소규모의 프로젝트나 상업적 용도가 아닌 경우에 적합.
  2. 강력한 성능:  AG Grid 는 많은 양의 데이터를 효율적으로 처리할 수 있는 그리드 컴포넌트로 기본적인 CRUD 작업을 포함하여 빠른 렌더링 성능을 지원.
  3. 광범위한 기능:
    • 기본적인 정렬, 필터링, 페이징, 그룹핑, 편집 등 그리드에서 필요한 대부분의 기능을 포함
    • 다양한 컬럼 레이아웃과 사용자 정의 셀 렌더러 등 유연한 데이터 표현이 지원
  4. 활발한 커뮤니티: AG Grid는 대규모 사용자 커뮤니티를 보유하고 있어 문제 발생 시 도움을 얻기 용의.
  5. 다양한 프레임워크 지원: React, Angular, Vue, 순수 자바스크립트 등 여러 프레임워크에서 사용할 수 있는 유연함

AG Grid Community 버전의 한계

  1. 고급 기능 제한: 커뮤니티 버전에는 고급 기능이 포함되어 있지 않음. 특히 아래 기능들이 프로 버전에만 포함.
    • 서버 사이드 모델(Server-Side Model): 대규모 데이터 집합에서 서버와 연동하여 그리드 데이터를 관리하는 기능.
    • 익스포트 기능: CSV, Excel 등의 형식으로 데이터를 내보내는 기능이 제한.
    • Tree Data 구조: 트리 구조의 데이터를 처리할 수 있는 고급 데이터 표현 방식 미지원.
    • 엔터프라이즈 레벨 필터링: 고급 필터링 기능은 프로 버전에만 포함.
  2. 기술 지원 부족: 커뮤니티 버전에서는 공식적인 기술 지원을 받을 수 없음. 문제 해결 시 커뮤니티에 의존해야 함.
  3. 사용 제한: 상업적 프로젝트에서 고급 기능이 필요하거나 성능 최적화 및 유지보수가 중요한 경우, 커뮤니티 버전만으로는 한계가 있을 수 있음.
AG Grid Community 버전은 무료로 사용할 수 있고, 기본적인 그리드 기능을 모두 제공하기 때문에 많은 프로젝트에서 유용하다. 그러나 고급 기능이나 대규모 데이터 처리가 필요한 경우 프로 버전으로 업그레이드하는 것이 필요하다. 

유료 버전에서는 rowModelType을 "serverSide" 로 설정하고 그리드가 준비되면 getRows 함수 내에서 서버 요청을 수행하고 결과를 그리드에 전달하는 데이터소스를 api.setServerSideDatasource() 함수를 사용하여 설정하는 방식으로 쉽게 구현할 수 있다.

AG Grid 커뮤니티 버전에서 서버 페이징은 (무한 스크롤을 구현하는 방법) rowModelType 값으로  "infinite" 을 사용하여 비슷한 방법으로 구현할 수 있다. 
  1. rowModelType을 "infinite"로 설정하여 그리드가 데이터를 한 번에 모두 로드하지 않고, 필요한 만큼 서버에서 요청하도록 한다.
  2. getRows 함수는 그리드가 추가 데이터를 요청할 때마다 서버에 데이터를 가져오는 역할을 한다.
  3. dataSource는그리드가 준비될 때 설정하고, 그리드가 데이터를 필요로 할 때마다 자동으로 서버에 데이터를 요청한다. ( 그리드 준비 여부는 onGridReady 이벤트를 사용)
개발 단계를 구체적으로 설명하면 아래와 같다.

설명 코드 예시
1 프로젝트에 AG Grid 설치 npm install --save ag-grid-community ag-grid-vue
2 컬럼 정의 및 그리드 옵션 설정 columnDefsgridOptions<script setup lang="ts">에서 정의하고 ref로 선언
3 Infinite Row Model 활성화 gridOptions에서 rowModelType'infinite'로 설정하고, paginationPageSizecacheBlockSize를 정의
4 onGridReady 함수에서 서버 데이터 소스 설정 onGridReady에서 getRows 함수가 포함된 dataSource를 설정
5 getRows 함수에서 서버 데이터 요청 params.startRow, params.endRow를 기반으로 페이지와 페이지 크기를 서버에 요청
6 서버 응답 처리 서버의 데이터 응답을 받아 params.successCallback(data.rows, data.totalCount)을 호출하여 그리드에 전달
7 Vue 컴포넌트 마운트 시 그리드 참조 설정 onMounted에서 gridRef를 통해 그리드 API에 접근하여 초기 설정 완료


매번 서버 페이징이 필요한 경우 위와 같이 코딩하는 것은 여간 불편한 것이 아니다. 이런 이유에서 추상 형태의 DataSource 을 정의하고 공통으로 적용할 설정을 미리 정의하고 필요한 값만 전달하여 페이징 그리드를 구현하는 컴포넌트를 만들어 보았다.


아래는 PageableGridContent.vue 에서 사용하는 서버에서 데이터를 가져오는 PageableDataSource 에 대한 추상 AbstractPageDataSource 이다. 이를 통하여 좀더 쉽게 서버에서 데이터를 가져오는 데이터소스 구현을 가능하게 한다. ( 구현체에서는 규칙을 벗어 나지 않는 경우라면 서버 엔트포인트 값을 리턴하는 getPatchUrl () 만 구현하면 된다. 서버 프로그램은 스프링의 Pageable  형식으로 데이터를 리턴하는 것을 가정 하여 데이터소스 추상을 구현 했다.


위의 컴포넌트를 사용하여 1) 컴포넌트 임포트 2)  그리드 컬럼 정의 3) 태그를 사용하여 컴포넌트를 정의하는 것으로 그리드를 간단하게 구현 할 수 있다. 또한 컴포넌트에 직접 제어가능한 함수를 사용하여 새로고침, 선택된 데이터 접근, 필터 초기화 등을 제어할 수 있다. 그리드 컬럼에서 단수 데이터 출력이 아닌 버튼과 또는 이미지 출력 등의 작업이 필요한 경우는 사용자 정의 더러를 정의하고 버튼 클릭등의 이벤트는 이벤트 리스트너를 통하여 구현하였다. 


다음은 위의 AbstractPageDataSource 활용하여 구현한 예이다. 아래는 목록을 조회하는 기능과 추가로 여러 함수를 포함하고 있다.

구현 클래스는 추가 기능이 필요하면 PageableDataSource 을 포함하여 새로운 타입을 정의하고 이를 구현하는 클래스를 AbstractPageableDataSource 상속하여 만든다. 구현 클래스는 getFetchUrl 함수를 구현하는 것으로 완성되며 this 이슈를 해결하기 위하여 반인딩을 사용하여 스토어를 생성하게 한다.
  1. 필요한 새로운 PageableDataSource 을 포함하는 type 생성
  2. getFetchUrl 을 포함 새로운 type 을 구현하는 클래스 생성 
  3. 구현 클래스를 사용하여 스토어 생성

import { RoleModel } from "@/types/studio/RoleModel";
import {
API_HEADERS,
authHeader
} from "@/util/helpers";
import axios from "axios";
import { defineStore } from "pinia";

import type {
PageableDataSource
} from "@/types/studio/index";
import { AbstractPageDataSource } from "../AbstractPageDataSource";

// getById,getByName,loadById,saveOrUpdate 추가을 추가하여 IPageableRoleDataSource를 정의
type IPageableRoleDataSource = PageableDataSource & {
getById: (roleId: number) => RoleModel;
getByName: (name: string) => RoleModel;
loadById: (roleId: number) => Promise<RoleModel>;
saveOrUpdate: (item: RoleModel) => Promise<void>;
};
const baseUrl = `${import.meta.env.VITE_API_URL}/data/secure/mgmt/security/roles:find`;

class PageableRoleDataSource
extends AbstractPageDataSource
implements IPageableRoleDataSource
{
getById(roleId: number) {
return this.dataItems.value.find(
(item: RoleModel) => item.roleId === roleId
);
}

getByName(name: string) {
return this.dataItems.value.find((item: RoleModel) => item.name === name);
}

async loadById(roleId: number) {
const headers = { ...API_HEADERS, ...authHeader() };
const category = await axios
.get(
`${import.meta.env.VITE_API_URL}/data/secure/mgmt/security/roles/${roleId}`,
{ headers: headers }
)
.then((response) => {
const data = response.data;
return data;
})
.catch((err) => {
this.alertStore.error(err);
});

return category;
}

async saveOrUpdate(item: RoleModel) {
const headers = { ...API_HEADERS, ...authHeader() };
await axios
.post(
`${import.meta.env.VITE_API_URL}/data/secure/mgmt/security/roles/${item.roleId}`,
JSON.stringify(item),
{ headers: headers }
)
.then((response) => {
const dataItems = response.data;
})
.catch((err) => {
this.alertStore.error(err);
});
}

// API 엔드포인트 URL을 제공. 이 클래스만 구현하면 그리드는 정상 동작한다.
getFetchUrl(): string {
return baseUrl;
}
}

// 필수로 표기된 부분은 반드시 동일하게 리턴해야 한다.
export const usePageableRolesStore = defineStore("mgmt-pageable-roles-store", () => {
const dataSource = new PageableRoleDataSource();
return {
isLoaded: dataSource.isLoaded, // 필수
dataItems: dataSource.dataItems, // 필수
total: dataSource.total, // 필수
pageSize: dataSource.pageSize,// 필수
getById: dataSource.getById.bind(dataSource),
getByName: dataSource.getByName.bind(dataSource),
loadById: dataSource.loadById.bind(dataSource),
saveOrUpdate: dataSource.saveOrUpdate.bind(dataSource),
setPage: dataSource.setPage.bind(dataSource),// 필수
setSort: dataSource.setSort.bind(dataSource),// 필수
setFilter: dataSource.setFilter.bind(dataSource),// 필수
fetch: dataSource.fetch.bind(dataSource), // 필수
};
});

예제 프로그램 실행 화면은 아래와 같다.


댓글 없음:

댓글 쓰기