SAPUI5 데이터 바인딩 완전 가이드 — Property, Aggregation, Expression Binding

Moderator · 조회 2

slide

slide

slide

SAPUI5 데이터 바인딩 완전 가이드 — Property, Aggregation, Expression Binding

SAPUI5 애플리케이션에서 모델(Model)과 뷰(View)를 연결하는 데이터 바인딩은 MVC 아키텍처의 핵심 축입니다. 이 가이드에서는 SAPUI5 1.120 LTS(2025 기준 최신 유지보수 버전) 환경을 기준으로, 바인딩의 원리부터 실전 패턴까지 단계별로 살펴봅니다.

1. 개요 및 학습 목표

데이터 바인딩이란 모델에 저장된 데이터를 UI 컨트롤의 속성, 집합(Aggregation), 또는 표현식과 자동으로 동기화하는 메커니즘입니다. 바인딩을 올바르게 활용하면 수동 DOM 조작 없이도 데이터 변경이 화면에 즉시 반영되므로, 코드 양이 줄고 유지보수가 쉬워집니다.

이 튜토리얼을 마치면 다음을 할 수 있습니다:

2. 선수 지식

3. 환경 / 버전 / 준비물

항목권장 사양
SAPUI5 버전1.120 LTS 이상 (OpenUI5도 동일 API)
개발 도구SAP Business Application Studio 또는 VS Code + Fiori Tools 확장
런타임Chrome/Edge 최신 버전
백엔드(3단계)OData V2/V4 서비스 — Northwind 샘플 서비스 사용 가능

프로젝트를 새로 만들 경우 yo @sap/fiori:project 제너레이터 또는 BAS의 "Start from Template"을 이용하면 manifest.json과 Component.js가 자동 생성됩니다. 이 가이드의 코드는 XML View + Controller 조합을 전제합니다.

4. 핵심 개념

4-1. 바인딩 모드 3가지

바인딩 모드는 데이터가 흐르는 방향을 결정합니다. 수도관에 비유하면 이해가 쉽습니다.

모드비유동작대표 사용처
One-Way단방향 파이프모델 변경 → UI 반영. UI 입력은 모델에 전달되지 않음읽기 전용 화면, ODataModel 기본값
Two-Way양방향 파이프모델 ↔ UI 양쪽 동기화입력 폼, JSONModel 기본값
One-Time스냅샷 사진최초 한 번만 값을 읽고, 이후 변경은 무시초기 설정값, 변하지 않는 레이블
JSONModel은 기본적으로 Two-Way, ODataModel V2는 기본적으로 One-Way입니다. 필요에 따라 oModel.setDefaultBindingMode()로 변경할 수 있습니다.

4-2. 바인딩 타입 4가지

SAPUI5에서 바인딩을 거는 대상에 따라 네 가지로 나뉩니다.

4-3. 모델 종류

모델데이터 위치기본 바인딩 모드적합한 상황
JSONModel클라이언트(브라우저 메모리)Two-Way소규모 데이터, 로컬 상태 관리
XMLModel클라이언트Two-WayXML 형식 데이터 처리
ODataModel (V2/V4)서버One-Way (V2 기본)대규모 데이터, 서버 사이드 정렬/필터/페이징

일반적으로 1,000행 이상의 데이터셋은 ODataModel 사용이 권장됩니다. 서버 사이드 페이징과 필터링을 활용하면 대규모 데이터에서 40~60% 수준의 성능 개선을 기대할 수 있습니다. 반면 JSONModel은 브라우저 메모리에 전체 데이터를 보관하므로 경량 데이터에 적합합니다.

5. 실전 코드 3단계

5-1단계: JSONModel + Property Binding + Element Binding

가장 기본적인 시나리오입니다. JSON 데이터를 만들고, 뷰에서 속성 바인딩과 엘리먼트 바인딩으로 표시합니다.

Controller (Main.controller.js)

sap.ui.define([
  "sap/ui/core/mvc/Controller",
  "sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
  "use strict";

  return Controller.extend("my.app.controller.Main", {
    onInit: function () {
      // 1) JSONModel 생성 및 데이터 설정
      var oModel = new JSONModel({
        Student: {
          id: "STU-001",
          name: "김민수",
          major: "컴퓨터공학",
          gpa: 3.8
        }
      });

      // 2) 뷰 레벨에 모델 설정
      this.getView().setModel(oModel);
    }
  });
});

View (Main.view.xml)

<mvc:View
  controllerName="my.app.controller.Main"
  xmlns:mvc="sap.ui.core.mvc"
  xmlns="sap.m"
  xmlns:l="sap.ui.layout">

  <!-- Element Binding: VerticalLayout 전체에 /Student 경로를 바인딩 -->
  <l:VerticalLayout binding="{/Student}">

    <!-- Property Binding: 상대 경로로 각 속성에 접근 -->
    <Label text="학번" />
    <Text text="{id}" />

    <Label text="이름" />
    <Input value="{name}" />

    <Label text="전공" />
    <Text text="{major}" />

    <Label text="학점" />
    <Text text="{gpa}" />
  </l:VerticalLayout>

</mvc:View>

동작 원리: binding="{/Student}"가 Element Binding이며, 레이아웃 하위의 {id}, {name} 등은 /Student를 기준으로 한 상대 경로 Property Binding입니다. JSONModel은 기본 Two-Way이므로, Input에 값을 입력하면 모델의 /Student/name이 자동으로 갱신됩니다.

5-2단계: Aggregation Binding (List + Table 템플릿, Factory)

배열 데이터를 반복 렌더링하는 Aggregation Binding을 실습합니다.

Controller에 배열 데이터 추가

onInit: function () {
  var oModel = new JSONModel({
    Products: [
      { ProductName: "노트북",   UnitPrice: 1200, Category: "전자기기" },
      { ProductName: "마우스",   UnitPrice: 25,   Category: "주변기기" },
      { ProductName: "모니터",   UnitPrice: 450,  Category: "전자기기" },
      { ProductName: "키보드",   UnitPrice: 80,   Category: "주변기기" },
      { ProductName: "서버랙",   UnitPrice: 3500, Category: "인프라" }
    ]
  });
  this.getView().setModel(oModel, "product");  // Named Model
}

XML 템플릿 방식 — List

<List
  headerText="제품 목록"
  items="{product>/Products}">
  <items>
    <ObjectListItem
      title="{product>ProductName}"
      number="{product>UnitPrice}"
      numberUnit="USD" />
  </items>
</List>

XML 템플릿 방식 — Table

<Table items="{product>/Products}">
  <columns>
    <Column><Text text="제품명" /></Column>
    <Column><Text text="가격" /></Column>
    <Column><Text text="카테고리" /></Column>
  </columns>
  <items>
    <ColumnListItem>
      <Text text="{product>ProductName}" />
      <Text text="{product>UnitPrice}" />
      <Text text="{product>Category}" />
    </ColumnListItem>
  </items>
</Table>

Factory 함수 방식 — 조건에 따라 서로 다른 컨트롤을 생성해야 할 때 사용합니다.

// Controller 내부
productFactory: function (sId, oContext) {
  var iPrice = oContext.getProperty("UnitPrice");

  if (iPrice > 1000) {
    // 고가 제품: ObjectListItem에 강조 표시
    return new sap.m.ObjectListItem(sId, {
      title: "{product>ProductName}",
      number: "{product>UnitPrice}",
      numberState: "Error",
      numberUnit: "USD"
    });
  }
  // 일반 제품: StandardListItem
  return new sap.m.StandardListItem(sId, {
    title: "{product>ProductName}",
    description: "{product>UnitPrice} USD"
  });
}
<!-- View에서 factory 속성 사용 -->
<List
  headerText="제품 목록 (Factory)"
  items="{
    path: 'product>/Products',
    factory: '.productFactory'
  }" />

참고: Aggregation Binding 시 기본 리스트 크기 제한이 100건인 경우가 있으므로, 대량 데이터에서는 items 바인딩에 length 파라미터를 확인하거나 growing/growingThreshold 속성을 활용하세요.

5-3단계: Expression Binding + ODataModel + Named Model + 바인딩 모드 제어

Expression Binding은 XML 뷰 안에서 삼항 연산, 비교, 문자열 연결 등 간단한 로직을 수행합니다.

<!-- 가격에 따라 상태 색상 변경 -->
<ObjectListItem
  title="{product>ProductName}"
  number="{product>UnitPrice}"
  numberState="{= ${product>UnitPrice} > 500 ? 'Error' : 'Success' }"
  numberUnit="USD" />

<!-- 재고 여부에 따라 버튼 활성화 -->
<Button
  text="주문하기"
  enabled="{= ${product>Stock} > 0 }" />

<!-- 문자열 결합 -->
<Text text="{= '총 금액: ' + ${product>UnitPrice} + ' USD'}" />

ODataModel 설정 (manifest.json)

{
  "sap.app": {
    "dataSources": {
      "mainService": {
        "uri": "/sap/opu/odata/sap/ZPRODUCT_SRV/",
        "type": "OData",
        "settings": {
          "odataVersion": "2.0"
        }
      }
    }
  },
  "sap.ui5": {
    "models": {
      "": {
        "dataSource": "mainService",
        "settings": {
          "defaultBindingMode": "TwoWay",
          "useBatch": true
        }
      },
      "local": {
        "type": "sap.ui.model.json.JSONModel",
        "settings": {
          "data": { "editMode": false }
        }
      }
    }
  }
}

위 설정에서 이름이 없는 모델("")은 ODataModel이고, "local"은 JSONModel입니다. 뷰에서는 다음과 같이 구분합니다:

<!-- 기본(이름없는) 모델: OData -->
<Input value="{/Products('001')/ProductName}" />

<!-- Named Model: local -->
<Button visible="{local>/editMode}" text="저장" />

바인딩 모드를 런타임에서 제어하기

// 모델 전체의 기본 바인딩 모드 변경
var oModel = this.getView().getModel();
oModel.setDefaultBindingMode(
  sap.ui.model.BindingMode.OneWay
);

// 개별 Property Binding의 모드 지정 (XML)
// <Input value="{path: '/name', mode: 'OneWay'}" />

모델 레벨 설정은 코어, 뷰, 컨트롤 순서로 적용 범위가 좁아집니다. 코어 레벨에 설정하면 앱 전체에, 컨트롤 레벨에 설정하면 해당 컨트롤에만 영향을 줍니다.

// 코어 레벨 (앱 전체)
sap.ui.getCore().setModel(oGlobalModel);

// 뷰 레벨 (해당 뷰 내부)
this.getView().setModel(oViewModel, "view");

// 컨트롤 레벨 (특정 컨트롤만)
this.byId("myInput").setModel(oSpecialModel);

6. 흔한 실수 / 트러블슈팅

Q1. 바인딩 경로를 올바르게 적었는데 화면에 아무것도 나오지 않습니다.

Named Model을 사용할 경우 경로 앞에 모델 이름을 반드시 붙여야 합니다. 예를 들어 setModel(oModel, "product")로 설정했다면 뷰에서는 {product>/Products}처럼 접두사를 사용해야 합니다. 이름 없이 {/Products}로 쓰면 기본 모델에서 경로를 찾으므로 값이 표시되지 않습니다.

Q2. Aggregation Binding에서 데이터가 100건만 나옵니다.

ODataModel의 기본 페이지 크기($top)나 List 컨트롤의 기본 제한이 원인일 수 있습니다. List/Table에 growing="true", growingThreshold="200" 속성을 추가하거나, OData 서비스의 $top 파라미터를 조정하세요. 대량 데이터(1,000행 이상)에서는 JSONModel 대신 ODataModel 사용이 권장됩니다.

Q3. Two-Way Binding인데 Input 변경이 모델에 반영되지 않습니다.

ODataModel V2의 기본 바인딩 모드는 One-Way입니다. manifest.json에서 "defaultBindingMode": "TwoWay"를 명시적으로 설정하거나, 컨트롤러에서 oModel.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay)를 호출하세요.

Q4. Expression Binding에서 syntax error가 발생합니다.

Expression Binding 내부에서 모델 값을 참조할 때는 반드시 ${경로} 형태를 사용해야 합니다. 예: {= ${product>UnitPrice} > 50 }. 또한 Expression Binding은 간단한 표현식만 지원하며, 복잡한 로직은 Formatter 함수를 사용하는 것이 일반적입니다.

Q5. Element Binding 설정 후 하위 컨트롤에서 절대 경로를 쓰면 어떻게 되나요?

절대 경로({/Student/name})는 Element Binding의 컨텍스트를 무시하고 모델 루트에서 탐색합니다. Element Binding의 이점을 활용하려면 상대 경로({name})를 사용하세요.

7. 다음 단계 / 관련 주제

데이터 바인딩의 기초를 익혔다면, 다음 주제로 확장해 보세요:

8. 참고 자료