라우팅 시스템과 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에 여러 가지를 동적으로 집어 넣을 수 있게 된다.