Tutorial: Tour of Heroes - Add Navigation
Add naviagtion with routing
듀토리얼에서 만들고 있는 앱에 아래의 기능을 추가할 거임.
- 대시보드 뷰
- 대시보드 뷰랑 Heroes 사이를 navigate할 수있는 기능
- 유저가 hero name을 어느 뷰에서든 클릭하면 selected hero에 대한
detail이 있는 뷰로 이동하는 기능 - 유저가 이메일의 deep link를 클릭하면 특정한 hero에 대해서
detail view를 열어주는 기능
Add the AppRouting Module
라우팅만 하는 탑레벨 모듈을 만들어서 AppModule
에 import 해줄거임.
일반적으로 모듈 클래스 이름은 AppRoutingModule
로 하고 이 클래스는
src/app 안에 있는 app-routing-module.ts
에 들어있음.
ng generate module app-routing --flat --module=app
위 명령어로 모듈 생성함.
--flat
은 파일을 자신만의 폴더를 만들어서 생성하지않고 src/app에 추가해줌.
--modlue=app
은 CLI가 AppModule
의 imports
배열에 모듈을 추가하라고 알려주는거임.
// src/app/app.module.ts
...
@NgModule({
...
imports: [
...
AppRoutingModule,
...
],
...
})
맨 처음에 CLI로 앱 생성할때
add routing = yes
해서 이미 app-routing.module.ts가 있어서
위 명령어 했을떄A merge conflicted on path "/src/app/app-routing.module.ts".
라고 나오긴 하는데
이미 있어서 그런거고 똑같은 파일이라서 에러는 상관없음.
routing 모듈은 아래처럼 생겼음.
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
위 코드를 아래처럼 수정해야함.
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
위 코드를 보면 app-routing.module.ts
파일은 RouterModule
과 Routes
를 import해서
앱이 routing 기능을 가지게 해줌. 다음으로 하는 import는 HeroesComponent
를 가져와서
라우터가 route를 configure했을때 갈 수있는 곳을 제공해줌.
Routes
app-routing.module.ts
의 다음 부분은 route를 configure 하는 부분임. Routes는 Router한테
유저가 링크를 클릭하거나 URL을 브라우저의 주소창에 넣어서 들어갈때 어떤 뷰를 보여줄지 알려줌.
app-routing.module.ts
가 HeroesCopmonent
를 import했기때문에 모듈에서 routes
array안에서
컴포넌트를 사용할 수 있음.
// src/app/app-routing.module.ts
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
일반적인 앵귤러 Route
는 2개의 property를 가지고 있음.
path
: 브라우저 주소 바에서URL
에 해당하는 string임.component
: 이 route로 왔을떄 라우터가 생성할 컴포넌트임.
따라서 위 코드는 라우터한테 URL로 localhost:4200/heroes
가 왔을때 HeroesComponent
를 display
하도록 알려줌.
RouterModule.forRoot()
@NgModule
메타데이터는 라우터를 초기화하고 라우터가 브라우저의 location 변화를 감지하도록함.
아래 코드는 AppRoutingModule
에 RouterModule
을 imports 배열에 추가후에
RouterModule.forRoot()을 이용해 RouterModule
을 routes
로 configure 하도록함.
다음으로 AppRoutingModule
은 RouterModule
을 export해서 RouterModule
을 앱 전체에서 사용가능하게함.
// src/app/app-routing.module.ts
...
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
...
forRoot()
메소드를 부르는 이유는 라우터를 앱의 root level에 configure 했기 때문임.
forRoot()
메소드는 라우팅에 필요한 service provider와 directive를 제공하고
현재 브라우저 URL에 기반해서 초기 navigation을 수행해줌.
// RouterModule이 정확히 머임?? router 모듈임?
Add RouterOutlet
AppComponent
템플릿을 열어서 <app-heroes>
element를 <router-outlet>
element로 대체해줌.
<!--
src/app/app.component.html
-->
<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>
AppComponent
템플릿은 유저가 HeroesComponent
로 navigate 할때만 HeroesComponent
를 보여줄 것이기에
<app-heroes
> component`가 더 이상 필요하지 않음.
<router-outlet>
안 라우터한테 어디가 routed된 view를 보여줄지 알려줌.
RouterOutlet
은 라우터 directive 중에 하나임. 이 directive는 아래에서 볼 수 있는 것처럼
AppRoutingModule이
RouterModule를 export하는데
AppRoutingModule을
AppModule이 import하기 때문에
AppComponent`에서 사용가능한 것임.
// src/app/app.module.ts
...
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// src/app/app.module.ts
...
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
...
@NgModule({
declarations: [
AppComponent,
...
],
imports: [
...
AppRoutingModule,
...
],
...
})
export class AppModule { }
ng generate module app-routing --flate --module=app
명령어에서 --module=app
플래그를
사용했기때문에 AppModule에서 import가 된거임.
app-routing.module.ts
를 따로 만들었거나 CLI가 아닌 툴로 만들었으면
AppRoutingModule
을 app.module.ts
에 import하고 @NgModule
의 imports
배열안에 추가해줘야함.
Try it
이제 브라우저가 새로고침하면서 localhost:4200
는 앱의 title은 보여주는데 hero의
list를 보여주지는 않음.
브라우저에서 주소창을 보면 http://localhost:4200/
로 /
로 끝나는것을 볼 수 있음. HeroesComponent
로 가는
route는 /heroes
임.
http://localhost:4200/heroes
로 들어가보면 이전과 똑같은 화면을 볼 수 있음.
Add a navigation link(routerLink)
보통 유저는 클릭을 통해서 사이트를 navigate하지 주소창에 URL을 직접 입력해서
navigate하지 않음.
<nav>
element를 추가하고 그 안에 클릭하면 HeroesComponent
로 navigate 해주는
anchor element(<a>)를 넣어줌.
<!--
src/app/app.component.html
-->
<h1>{{title}}</h1>
<nav>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
routerLink
attribute는 "./heroes"로 set 됐는데 이 스트링은 라우터가 route를
HeroesComponment
로 매칭하게 해주는 스트링임. routerLink
는 유저의 클릭을 router navigation으로 바꿔주는 RouterLink
디렉티브의 selecotr임.
@angular/router
의RouterLink
directive는 템플릿의 element에 적용되면 해당 element를 route로 navigate해주는 링크로 만들어줌.
Add a dashboard view
뷰가 많으면 많을수록 routing의 장점이 돋보임. 지금까지는 heroes 뷰 밖에 없었는데 아래 명령어로 DashboardComponent
를 추가해줄 거임.
ng generate component dashboard
DashboardComponent
템플릿을 아래처럼 수정해줌.
템플릿은 hero name으로 된 링크들을 만들어줌.
아직은 링크눌러도 아무곳으로도 안감.
<!--
srp/app/dashboard.component.html
-->
<h2>Top Heroes</h2>
<div class="heroes-menu">
<a *ngFor="let hero of heroes">
{{hero.name}}
</a>
</div>
DashboardCompnent
클래스를 아래처럼 수정해줌.
클래스는 HeroesComponent
클래스랑 비슷함.
heroes
배열 property가 있고- constructor는 앵귤러가
HeroService
를heroService
property로 inject하는것을 expect함. ngOnInit()
lifecycle hook은getHeroes()
를 호출함.
// src/app/dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit(): void {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
}
getHeroes()
는 hero 리스트에서 잘라내서 인덱스 1부터 4까지의 상위 4개의 hero만 리턴해줌.
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
Add the dashboard route
dashboard를 navigate 하기 위해서 router는 적절한 route가 필요함.
DashboardComponent
를 app-routing-module.ts
에 import 해주고 routes
배열에 DashboardComponent
에 path를 매치해주는 route를 추가해줌.
// src/app/app-routing.module.ts
import { DashboardComponent } from './dashboard/dashboard.component';
const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent }
];
Add a default route
앱이 시작할때 브라우저의 주소창은 웹사이트의 root을 가리킴. 지금 현재는 root가 존재하는 route에 매칭되는게 없기 때문에 router가 아무곳으로도 navigate해주지 않음.
지금 보면 <router-outlet>
부터 아래는 아무것도 나오지 않고 있음.
앱이 dashboard로 자동적으로 navigate하게 하려면, 아래 route를 routes
배열에 추가 하면됨.
// src/app/app-routing.module.ts
const routes: Routes = [
...
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }
];
이렇게하면 empty path에 fully match되는 URL을 path가 /dashboard
인 곳으로 리다이렉트해줌.
이제 브라우저가 새로고침하면서 DashboardComponent
를 로드하고 브라우저 주소창을 보면 localhost:4200/dashboard
로 바뀐것을 볼 수 있음.
Add dashboard link to the shell
유저는 페이지 상단의 navigation area의 링크를 눌러서 DashboardComponent
랑 HeroesComponent
를 왔다갔다 할 수 있어야함.
따라서 AppComponent
shell 템플릿의 Heroes 링크 바로 위에 dashboard navigation을 추가해줌.
<!--
src/app/app.component.html
-->
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
브라우저가 새로고침 되면 링크를 클릭하는걸로 자유롭게 DashboardComponent
랑 HeroesComponent
로 이동할 수 있음.
Navigating to hero details
HeroDetailComponent
는 선택한 hero의 detail을 보여줌. 지금은 HeroDetailComponent
가 HeroesComponent
의 아래 부분에만 보이게 해놨음.
하지만 이제는 유저가 HeroDetailComponent에 아래 3가지 방법으로 접근하게 만들것임. 1. dashoboard에 있는 hero 클릭 2. 원래처럼 heroes list에 있는 hero 클릭 3. 원하는 hero에 대한
deep link` URL을 브라우저 주소창에 입력하기
이번 섹션에서는 위 3가지 방법으로 HeroDetailComponent
로 navigate할 수 있게 만들면서 HeroesCopmonent
에서 HeroDetailComponent
를 떼어낼것임.
Delete hero details from HeroesComponent
유저가 HeroesComponent
의 hero를 클릭하면 앱은 HeroDetailComponent
로 이동하고 heros list 뷰를 hero detail 뷰로 바꿔주도록 할거임. heroes list 뷰는 hero detail을 이전처럼 보여주지 않을것임.
HeroesComponent
템플릿을 열어서 ` element을 삭제해 줌. 이러면 hero list에서 hero를 클릭해도 이전처럼 hero detail을 보여주지 않음.
Add a hero detail route
id가 11인 hero의 Hero Detail
뷰로 갈때 ~/detail/11
같은 URL은 좋은 URL임.
app-routing.module.ts
를 열어서 HeroDetailComponent
를 import 해줌.
추가로 hero detail 뷰에 해당하는 path patern인 parameterized route를 routes
array에 추가함.
// src/app/app-routing.module.ts
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
...
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'detail/:id', component: HeroDetailComponent }
];
...
path
의 콜론(:)은 :id
가 특정한 hero의 id
에 대한 placeholder라는 것을 나타내줌.
DashboardComponennt hero links
DashboardComponent
의 상위 4개 hero에 대한 링크는 현재 아무 기능이 없음.
이제 라우터가 HeroDetailComponent
에 대한 route를 가지게 됐으니 dashboard hero link가 parameterized dashboard route로 navigate 하도록 수정해야함.
<!--
src/app/dashboard/dashboard.component.html
--->
<h2>Top Heroes</h2>
<div class="heroes-menu">
<a *ngFor="let hero of heroes"
routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</div>
위 코드에서 앵귤러의 interpolation binding를 *ngFor
repeater 안에서 사용해서 현재 반복의 hero.id
를 routerLink
에 넣어주고 있음.
HerosComponent hero links
HeroesComponent
에 있는 <li>
element의 hero item들은 클릭 이벤트가 HeroesCopmonent
의 onSelect()
메소드에 bind 되어있는데 dashboard 템플릿처럼 버튼 말고 <a>
로 바꿔줌.
<!--
src/app/heroes/heroes.component.html
-->
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
</li>
</ul>
이제 컴포넌트의 private stylesheet를 수정해서 이전의 버튼처럼 보이도록 바꿔줘야함.
Remove dead code(optional)
HeroesComponent
클래스가 작동은 하지만, onSelect()
메소드랑 selectedHero
property는 더 이상 사용을 안하니 삭제해주기.
// 이거 messageService도 그러면 안쓰게 되는데 삭제하는거 찾아보기
// src/app/heroes/hereos.component.ts
export class HeroesComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit(): void {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
Routable HeroDetailComponent
이전에 parent인 HeroesComponent
가 HeroDetailComponent.hero
property를 set해줘서 HeroDetailComponent
가 hero를 보여줬었는데 다 수정해서 이제는 그렇게 보여주지 않음. 이제 라우터가 ~/detail/
같은 URL이 들어오면 HeroDetailComponent
를 생성해줌.
<!--
src/app/heroes/heroes.component.html
parent-child 관계였던 이전 코드임
-->
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
// src/app/hero-detail/hero-detial.component.ts
// parent-child 관계였던 이전 코드임
@Input() hero?: Hero;
그렇게 하려면 아직 더 수정할게 남았음. HeroDetailComponent
는 위의 이전 코드에서 했던 방식 말고 다른 방법으로 보여줄 hero를 가지고 와야함.
이 섹션에서는 아래 방법들을 설명해줌.
- 생성할 route를 가져오는 법
- route에서
id
추출하기 - 추출한
id
와HeroService
를 이용해서 hero 가져오기 아래와 같은 import 해주기.// src/app/hero-detail/hero-detail.component.ts import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { HeroService } from '../hero.service';
ActivatedRoute
,HeroService
그리고Location
서비스를 constructor에 private field로 선언해줌.// src/app/hero-detail/hero-detail.component.ts constructor( private route: ActivatedRoute, private heroService: HeroService, private location: Location ) {}
ActiveRoute
는HeroDetailComponent
인스턴스에 대한 route 정보를 가지고 있음.HeroDetailComponent
는 URL에서 route의 parameter(id
)를 추출하고 싶어함.
"id" parameter는 보여줄 hero의id
를 말함.HeroService
는 remote 서버에서 hero data를 가져오니HeroDetailComponent
서비스를 이용해서 보여줄 hero를 가져옴.location
은 앵귤러의 서비스로 브라우저와 상호작용하게 해줌.HeroDetailComponent
뷰로 왔을때 다시 뒤로 navigate 하기 위해 사용할 거임.Extract the id route parameter
ngOnInit()
lifecycle hook에서getHeor()
를 부르도록 아래처럼 해줌.
// src/app/hero-detail/hero-detail.component.ts
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
route.snapshot
컴포넌트가 생성된 직후의 route information의 static image임.
paramMap
은 URL에서 추출한 route paramter value의 dictionary임. "id"
키는 가져올 here의 id
를 리턴함.(https://angular.io/api/router/ParamMap)
interface ParamMap { keys: string[] has(name: string): boolean get(name: string): string | null getAll(name: string): string[] }
route parameter는 언제나 스트링이어서 자바스크립트 Number
함수로 number로 바꿔줌.
브라우저를 새로고침하면 아직까지는 HeroService
에 getHero()
메소드가 없기때문에 컴파일 에러가 나옴.
Add HeroService.getHero()
HeroService
를 열어서 getHero()
메소드를 추가해줌.
// src/app/hero.service.ts
getHero(id: number): Observable<Hero> {
// For now, assume that a hero with the specified `id` always exists.
// Error handling will be added in the next step of the tutorial.
// 화살표 함수는 iterable 한거 다 돌아서 아래되는거임.
const hero = HEROES.find(h => h.id === id)!;
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(hero);
}
getHeroes()
와 비슷하게 getHero()
는 asynchronous signature를 가지고 있음. 이 메소드는 RxJS의 of()
함수를 이용해서 mock hero를 Observable
로 리턴함.
나중에 getHero()
를 Http
request를 사용하도록 바꿀거임. 서비스로 데이터를 가져오는게 분리되어있기때문에 바꿀떄 HeroDetailComponent
를 수정할 필요없이 HeroService
만 바꿔주면됨.
Try it
브라우저가 새로고침되면서 앱이 다시 제대로 작동할 거임. dashboard의 hero나 herolist의 hero를 클릭해서 hero detail 뷰로 이동할 수 있음.
localhost:4200/detail/11
로 가게되면 라우터가 id가 11인 hero의 detail 뷰로 navigate 해줌.
Find the way back
브라우저의 뒤로가기 버튼을 누르면 hero list나 dashboard 뷰로 돌아갈 수 있지만 HeroDetail
뷰에 뒤로가기 버튼을 만들어서 뒤로 갈 수 있게 만드는 것도 좋은 생각임.
HeroDetailComponent
템플릿의 맨 아래에 뒤로 가기 버튼을 만들고 컴포넌트의 goback()
메소드를 bind 해줌.
<!--
src/app/hero-detail/hero-detail.component.html
-->
<button type="button" (click)="goBack()">go back</button>
goback()
메소드도 추가해줌.
// src/app/hero-detail/hero-detail.component.ts
goBack(): void {
this.location.back();
}
location.back()은 Navigates back in the platform's history 하게 해줌.
Summary
- 앵귤러 router를 추가해서 컴포넌트들을 navigate 할 수 있게 했음
AppComponent
를<a>
링크들과<router-outlet>
를 이용해서 navigation shell로 변경했음- `AppRoutingModule에 router를 configure했음
- route랑 redirect rout, parameterized route를 define 했음
routerLink
directive를<a>
안에 사용했음- master/detail(HeroesComponent랑 HeroeDetailCopmonent)뷰를 routed detail view로 바꿨음.
- router link parameter를 사용해서 선택한 hero의 detail view로 이동할 수 있게 했음
HeroService
를 여러 컴포넌트에서 사용하게 했음
Author And Source
이 문제에 관하여(Tutorial: Tour of Heroes - Add Navigation), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@oem0404/Tutorial-Tour-of-Heroes-Add-Navigation저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)