Ember

Ember - Tutorial(Router & controller)

모_아이 2024. 4. 25. 14:56

라우팅 시스템과 Router와 controller는 밀접한 연관이 있으니 바로 다뤄보는 게 좋을 것 같다.

유튜브 ember tutorial에서 만드는 페이지로 연습을 같이 해보려고 하는데 일단 이전에 만든 필요없는 경로를 삭제해보자


router 삭제

당연하게도 이를 도와주는 emberCLI가 존재하는데


ember destroy route clothes/index && ember destroy route clothes/t-shirt && ember destroy route clothes

clothes경로에는 중첩된 부분이 많기 때문에 중첩된걸하나하나 지워가며 마지막에 가장 큰 경로를 삭제하는 게 좋다.

바로 큰 경로를 삭제해봐야 폴더 내부는 안지워지고 `clothes.hbs`와 테스트 등 `clothes` 경로에 관한것만 지워지지 `clothes`폴더에 있는 중첩된 경로들까지 삭제해주진 않는다.

새 router 추가

이전에 쓸모없는 경로들을 삭제했으니 이제 웹 페이지에 들어갈만한 경로들을 만들자.

먼저 홈페이지인 애플리케이션 인덱스 페이지 경로인 인덱스를 생성하고 장바구니로 쓸 cart부분을 만들자

물론 emberCLI로 만든다

ember g route index && ember g route cart

그리고 cart경로의 경우 실제 경로 이름에선 좀 더 명확하게 해주기 위해서 다룰 파일의 이름은 쉽고 경로 이름은 명확하게 서로 다른 옵션을 사용하기 위해 router.js에서 변경해준다

Router.map(function () {
  this.route('item', { path: 'item/:item_id' });
  this.route('not-found', { path: '/*path' });
  this.route('cart', { path: 'shopping-cart' });
});

이처럼 입력할 경우 경로는 /shopping-cart가 되지만 실제 동작은 cart가 받아서 하게된다

이때 index route는 왜 없나요?? 할 수 있다
이는 index일 경우 default 페이지 역할을 하기 때문에 최상단 application.hbs 와 동일한 뎁스에 index.hbs를 가졌음으로 맨 처음 뒤에 어떠한 추가 경로도 적지 않고 말 그대로 홈페이지에 접속할 경우 이 해당 index.hbs로 접속할 수 있다.
현재 로컬에선 http://localhost:4200으로 접속시 application.hbs{{outlet}}부분에 index.hbs가 뜨게 되는 것이다.

Navigate pages

이제 홈페이지로 표현될 index.js에 상품 목록들을 보이게 하고 상품 목록들을 클릭하면 상품 상세보기로 점프 할 수 있게 하려고 한다.

먼저 application.hbs에 있는 코드들을 {{outlet}}을 제외하고 모두 지운 뒤 index.js에서 원하는 목록모양으로 HTML작성한다

<main class="container mt-5">
  <div>Product 1</div>
  <div>Product 2</div>
</main>

일단은 이렇게 작성하였는데 우리가 원하는 건 저 Product 1부분을 클릭했을 때 해당 제품의 상세페이지로 점프하는 것이다.

이를 어떻게 만들 수 있을까?
Next.js로 할땐 Link라는 녀석이 있었는데 ember도 비슷한 놈이 있다

바로 LinkTo이다

그러면 Next의 Link 처럼 href로 경로를 나타내냐? 이건 또 아니고 @route라는 걸 사용한다

<main class="container mt-5">
  <LinkTo @route="item" @model="1">Product 1</LinkTo>
  <LinkTo @route="item" @model="2">Product 2</LinkTo>
</main>

이 경우 제품을 클릭할 때 이동되는 경로는 item이 되고 이동할때 @model="1"을 통해 id값을 넘겨주게 된다

이처럼 LinkTo로 바꾼 페이지 모양은 html변환시 a태그와 동일하다

이런식으로 LinkTo를 통해 각 경로를 이동할 수 있고 .hbs에 원하는hmtl을 작성하여 템플릿을 렌더링 할 수 있다.

이걸 통해 각 cart와 product부분을 만든 후 cart.hbs에서 결제를 시킬 check out이라는 버튼을 한번 만들어보자.

  <button type="button" class="btn btn-success float-right">Check out</button>

이렇게 일단 button 은 만들어 놓고 버튼이 클릭 되었을 때 동작해야할 기능들은 어디에 적어야 할까?
기존 react나 next같은 경우 현재 함수형 컴포넌트들을 통해 같은 함수 스코프 안에 return 상단에 여러 기능들을 하는 함수들을 적어서 onClick에 넣는 식으로 사용했었는데 ember는 좀 모양새도 많이 달랐다

이는 먼저 Route와 Controller를 알아보고 추후에 추가해보자


Router와 Controller

라우터와 컨트롤러의 차이점은 동일한 URL에 대해 작동하고 이름도 같지만 다른 폴더에 존재한다.

  • Route
    • route/cart.js
  • Controller
    • controller/cart.js

이렇게 서로 cart.js라는 같은 이름을 가지지만 각각 RouteController라는 다른 폴더에 존재한다.
둘 다 app 경로 아래에 존재하며 template/cart.hbs를 렌더링한다.

Route에선 controller에 모델을 관여할 수 있으며 다양한 Methods들이 존재하는데 이들을 사용할 수 있다.

한번 다이나믹 라우팅을 사용하는 item은 product LinkTo로 이동할 때 id도 보내는데 이를 어떻게 받을까?
지난 글에서 썼었던? route/item.js를 활용해보자

// app/route/item.js
import Route from '@ember/routing/route';

export default class ItemRoute extends Route {
  model(params) {
    const { item_id } = params;

    return item_id;
  }
}

// app/templates/item.hbs
<h2>{{this.model}} product</h2>

this.model처럼 사용할 수 있는 건데 이걸 통해 routes/cart.js도 변경해보자

import Route from '@ember/routing/route';

export default class CartRoute extends Route {
  model() {
    const items = [{ price: 10 }, { price: 15 }];
    return items;
  }
}

만약 이런식으로 여러 가격을 가지고 있는 상품들이 있다고 생각하고 작성해보았다
그리고 controller를 사용해보려고 하는데 우리는 아직 만들지 않았으니 ember CLI로 만들어보자.

ember g controller cart

이렇게 controllercart를 만든후 템플릿에 전달할 몇가지 사용자 정의 속성을 만들어보자

// app/contorollers/cart.js
import Controller from '@ember/controller';

export default class CartController extends Controller {
  subtotla = 0;
  tax = 0;
  total = 0;
}

cart라는 장바구니 페이지에서 사용될 상품가격, 세금, 총 가격을 CartController에 기본적으로 정의하고 templates에 있는 cart.hbs에 가서 정의된 속성들을 사용해보자

 <section class="w-50 ml-auto text-right mb-5">
    <div class="row">
      <span class="col">Subtotal</span>
      <span class="col">{{this.subtotal}}</span>
    </div>
    <div class="row">
      <span class="col">Tax</span>
      <span class="col">{{this.tax}}</span>
    </div>
    <div class="row">
      <span class="col">Total</span>
      <span class="col">{{this.total}}</span>
    </div>
  </section>

여기서 {{this.subtotal}}사용 된 this는 현재 컨트롤러 또는 현재 라우팅을 가리키고 이를 저장하고 앱을 실행하면 표시되는 페이지가

이렇게 표시되는 것을 볼 수 있다.
이 숫자 0들은 우리가 Contoroller에서 정의한 값들이다. 하지만 아직 정적인 값들이니 이 값들을 실제로 관리해보기 위해 route/cart.js로 이동해서 코드를 추가해보자

// routes/cart.js

import Route from '@ember/routing/route';

export default class CartRoute extends Route {
  model() {
    const items = [{ price: 10 }, { price: 15 }];
    return items;
  }

  setupController(controller, model) {
    super.setupController(controller, model);
    const subtotal = model.reduce((acc, item) => acc + item.price, 0);
    controller.set('subtotal', subtotal);
  }
}

 

이는 먼저 setupController를 통해 컨트롤러 기능을 재정의 하는 메서드를 사용한다.
이때 setupController는 이미 있는 걸로 재정의 할 때 매개변수는 controller,model 을 사용할 것이다.
그리고 super를 사용해 super.setupController()로 상속된 모든 항목이 호출됐는지 확인해본다

이후 subtotal을 계산하기 위해 model을 가져온다. 이때 모델은 위에서 return 된 items가 된다.
이후 controller.set을 통해 controller에서 정의된 subtotal의 값을 변경한다

하지만 컨트롤러가 모델에 직접적으로 엑세스 할 수 있는 걸 발견했으니 굳이 route에서 작업하지 않고 바로 controller에서 작업이 가능하단 걸 깨달을 수 있다.
작업한 결과물을 잘라내서 controller에 붙여넣어보자

// controllers/cart.js

import Controller from '@ember/controller';

export default class CartController extends Controller {
  get subtotal() {
    return this.model.reduce((acc, item) => acc + item.price, 0);
  }
  get tax() {
    return 0.09 * this.subtotal;
  }
  get total() {
    return this.subtotal + this.tax;
  }
}

 

이처럼 getter를 통해서 각각의 subtotal, tax, total등을 계산하고 정의했고 이는 화면에 똑같이 출력되는 걸 확인해볼 수 있다.

 

 

이렇게 controller와 route를 통해서 우리가 사용하는 render에 여러 가지를 동적으로 집어 넣을 수 있게 된다.

 

반응형