문제 상황
Vue3 + ag-grid 환경에서 테이블을 만들고 있었다. 요구사항은 간단했다.
- 컬럼이 컨테이너보다 넓으면 가로 스크롤
- 스크롤바는 표의 맨 하단에 고정
- 데이터가 없으면 스크롤바 숨김
- 데이터가 없어도 표 영역은 화면 아래까지 채우기
간단할 줄 알았는데, ag-grid의 domLayout 설정에 따라 전혀 다르게 동작해서 꽤 고생했다.
domLayout: autoHeight의 함정
ag-grid의 기본적인 높이 제어 방식은 두 가지다.
domLayout: "autoHeight" // 행 수에 맞춰 높이 자동 조정
domLayout: "normal" // 컨테이너 높이에 맞춤 (스크롤 포함)
처음에는 autoHeight를 썼다.
행이 적어도 표가 자연스럽게 보이니까. 그런데 문제가 생겼다.
1. 스크롤바가 데이터 바로 밑에 붙는다
autoHeight에서는 ag-grid가 행 높이만큼만 영역을 잡기 때문에, 가로 스크롤바가 마지막 행 바로 아래에 위치한다. 표 하단이 아니라 데이터 끝에 따라다니는 셈이다.
2. 데이터가 없어도 스크롤바가 보인다
autoSizeColumns()로 헤더 텍스트 기준으로 컬럼 너비를 잡으면, 헤더만으로도 컨테이너를 초과해서 가로 스크롤이 생긴다. "표시할 데이터가 없습니다" 상태인데 스크롤바가 떡하니 있는 거다.
domLayout: normal로 전환... 그런데 표가 사라졌다
normal 모드로 바꾸면 스크롤바가 표 하단에 고정된다. 문제 해결! 이라고 생각했는데 표가 아예 안 보였다.normal 모드에서는 ag-grid에 명시적인 높이가 필요하다. 보통 이렇게 한다:
.ag-theme-alpine {
height: 100%;
}
그런데 height: 100%는 부모에 높이가 있어야 동작한다. 내 DOM 구조는 이랬다:
.content-box
└ .pad\_0\_10
└ .row
└ .col-lg-12
└ .table-section
└ .ag-theme-alpine ← height: 100% 인데 부모 높이가 0
중간 경로의 어떤 요소에도 height나 flex: 1이 없어서, 100%가 결국 0px이 된 거다.
해결: flex 체인 연결
부모부터 ag-grid까지 빈틈없이 flex로 연결했다.
** content-box부터 table-section까지 flex 체인 **
.svc-user-list .content-box {
display: flex;
flex-direction: column;
}
.svc-user-list .content-box > .pad_0_10 {
flex: 1;
display: flex;
flex-direction: column;
}
.svc-user-list .content-box > .pad_0_10 > .row:last-child {
flex: 1;
display: flex;
}
.svc-user-list .content-box > .pad_0_10 > .row:last-child > .col-lg-12 {
flex: 1;
display: flex;
flex-direction: column;
}
.svc-user-list .table-section {
flex: 1;
overflow: hidden;
}
.svc-user-list .table-section .ag-theme-alpine {
height: 100%;
width: 100%;
}
핵심은 하나라도 끊기면 안 된다는 것이다. flex: 1이 중간에 빠지면 높이 전달이 멈추고, height: 100%는 다시 0이 된다.
문제 2: 데이터 유무에 따른 컬럼 너비 전략
flex 체인을 연결하고 나니, 마지막 문제가 남았다. 데이터가 없을 때도 가로 스크롤이 보이는 것.
원인은 `autoSizeColumns()`가 헤더 텍스트 기준으로 컬럼을 넓히기 때문이다.
"사용자 휴대전화" 같은 긴 헤더가 컨테이너를 넘겨버린다.
해결은 단순했다.
데이터 유무에 따라 분기:
const autoSizeAll = () => {
let hasRows = false;
gridApi.value.forEachNode(() => { hasRows = true; });
if (hasRows) {
// 데이터 있으면: 내용 기준으로 컬럼 너비 자동 조정
const allColIds = gridColumnApi.value.getAllColumns().map(col => col.colId);
gridColumnApi.value.autoSizeColumns(allColIds, false);
} else {
// 데이터 없으면: 컨테이너에 맞춤 → 스크롤 안 생김
gridApi.value.sizeColumnsToFit();
}
};
그리고 setRowData() 직후에 autoSizeAll()을 호출하는 것이 중요하다.
처음에 이걸 `onGridReady`에서만 호출하고, 검색 결과 변경 시에는 빼먹어서 한참 헤맸다.
const searchServiceUsers = async () => {
const { items, count } = await API.requestServiceUsers(...);
gridApi.value.setRowData(items);
// 이걸 빼먹으면 이전 컬럼 너비가 유지되면서 스크롤이 안 사라진다
autoSizeAll();
};
정리
| 항목 | autoHeight | normal |
|---|---|---|
| 높이 | 행 수에 맞춤 | 컨테이너에 맞춤 |
| 가로 스크롤 위치 | 데이터 바로 밑 | 표 하단 고정 |
| 빈 상태 | 표 영역 축소 | 표 영역 유지 |
| 필요 조건 | 없음 | 부모 높이 필수 (flex 체인) |
- `autoHeight`는 간편하지만 스크롤 제어가 어렵다
- `normal`은 스크롤이 깔끔하지만 부모부터 ag-grid까지 높이 체인이 하나라도 끊기면 표가 사라진다
- 데이터 유무에 따라 autoSizeColumns/ sizeColumnsToFit 분기가 필요하다
- `setRowData()` 후 반드시 컬럼 너비 재계산을 호출해야 한다
'개발자로 살아남기 > 트러블슈팅' 카테고리의 다른 글
| [AIoT] 게이트웨이가 뭐길래? Level vs Edge Triggered 알람 이슈 (1) | 2026.04.21 |
|---|---|
| [Git] 브랜치 간 변동 사항을 옮길 때 주의할 점 (0) | 2026.04.06 |
| [JS] Cannot read properties of null (reading 'shapeFlag') - setTimeout과 디바운스 (0) | 2026.03.25 |
| [JS] Promise.reject()와 setTimeout (0) | 2026.03.25 |