라우팅 시스템과 Router와 controller는 밀접한 연관이 있으니 바로 다뤄보는 게 좋을 것 같다.
유튜브 ember tutorial에서 만드는 페이지로 연습을 같이 해보려고 하는데 일단 이전에 만든 필요없는 경로를 삭제해보자
당연하게도 이를 도와주는 emberCLI가 존재하는데
ember destroy route clothes/index && ember destroy route clothes/t-shirt && ember destroy route clothes
clothes경로에는 중첩된 부분이 많기 때문에 중첩된걸하나하나 지워가며 마지막에 가장 큰 경로를 삭제하는 게 좋다.
바로 큰 경로를 삭제해봐야 폴더 내부는 안지워지고 `clothes.hbs`와 테스트 등 `clothes` 경로에 관한것만 지워지지 `clothes`폴더에 있는 중첩된 경로들까지 삭제해주진 않는다.
이전에 쓸모없는 경로들을 삭제했으니 이제 웹 페이지에 들어갈만한 경로들을 만들자.
먼저 홈페이지인 애플리케이션 인덱스 페이지 경로인 인덱스를 생성하고 장바구니로 쓸 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가 뜨게 되는 것이다.
이제 홈페이지로 표현될 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를 알아보고 추후에 추가해보자
라우터와 컨트롤러의 차이점은 동일한 URL에 대해 작동하고 이름도 같지만 다른 폴더에 존재한다.
이렇게 서로 cart.js라는 같은 이름을 가지지만 각각 Route와 Controller라는 다른 폴더에 존재한다.
둘 다 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
이렇게 controller에 cart를 만든후 템플릿에 전달할 몇가지 사용자 정의 속성을 만들어보자
// 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에 여러 가지를 동적으로 집어 넣을 수 있게 된다.