ANgularJS 2.0+

[AngularJS 2.0] AngularJS 2.0 Tutorial3_routing

k9e4h 2016. 9. 19. 15:04

Angular.io 에서 제공하는 튜토리얼을 정리한 것입니다.

사진 클릭시 튜토리얼로 이동

!! RC4기준 !!

6. Routing (1) - 파일 분리하기

1. 이전 프로젝트와 달라지는 것

  • 대시보드 화면 추가
  • Heroes와 대시보드 화면 이동
  • 선택된 히어로의 디테일을 다른 화면에서 보여주기
  • clicking a deep link in an email opens the detail view for a particular hero



2. 폴더 구조





3. Plan


  •  AppComponent를 네비게이션만 다루는 하나의 application shell 로 변경
  •  Heroes concerns을 AppComponent와 HeroesComponent로 나누어 재위치
  •  routing 추가
  •  DashboardComponent 생성
  •  Tie Dashboard into the navigation structure



 routing 

의 다른 이름은 navigation 이다. 라우팅은 view와 view 사이의 navigationg 역할을 한다.




4. heroes.component.ts




Go back to the HeroesComponent and remove the HeroService from its providers array. We are promoting this service from the HeroesComponent to the AppComponent. We do not want two copies of this service at two different levels of our app.



파일 복사하면 안됨! 



HeroesComponent


  •  app.component.ts 파일의 이름을 heroes.component.ts로 변경 

  •  main.ts에 app.component.ts가 있다고 경고 뜰 것이지만 main.ts는 변경하지않고 진행, 뒤에서 app.component.ts 생성 할 것임

  •  AppComponent 클래스 이름을 HeroesComponent로 변경

  •  Selector를 my-app 에서 my-heroes로 변경




5. app.component.ts (version1)



Create AppComponent


  •  app.component.ts 라는 이름의 파일 생성
  •  AppComponent 클래스 정의
  •  export it so we can reference it during bootstrapping in main.ts
  •  my-app 이라는 selector를 가지는 @Component 생성하기
  •  template에 title을 <h1> 태그로 둘러싸기
  •  template에 my-heroes 태그 선언하기 (hero list도 여전히 보고 싶으니까)


 directives: [HeroesComponent]

Angular가 HeroesComponent를 인식할 수 있도록 선언


providers: [HeroService]

모든 다른 화면에서 HeroService에서 필요로 함


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component }       from '@angular/core';
import { HeroService }     from './hero.service';
import { HeroesComponent } from './heroes.component';
@Component({
    selector: 'my-app',
    template: `
    <h1>{{title}}</h1>
    <my-heroes></my-heroes>
  `,
    directives: [HeroesComponent],
    providers: [
        HeroService
    ]
})
export class AppComponent {
    title = 'Tour of Heroes';
}
cs


6. 라우팅 추가하기



angular의 router는 service, directive,configuration의 혼합이다.



< app.routes.ts >

import{ provideRouter, RouterConfig} from '@angular/router';
import {HeroesComponent} from './heroes.component';

const routes: RouterConfig = [
{
path:'heroes',
component: HeroesComponent
}
];

export const appRouterProviders=[
provideRouter(routes)
]


 path 

the router matches this rout's path to the URL in the brower address bar


 component 

the component that the router should create when navigating to this route


routes는 요청 URI path와 Component를 Mapping 한다.


< main.ts >

import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';

bootstrap(AppComponent,[
appRouterProviders
]);


Component Router는 서비스이다. 그러므로 main.ts의 bootstrap Array 에 추가해 줘야함


 bootstrap  

bootstrap은 두번째 인자로 injector binding의 목록을 가진다. injector 가 생성되었을때 바인딩이 사용된다. 즉 routerInjectables을 통과하는 모든 바인딩은 응용 프로그램 전체에 사용할 수 있다.




<app.component.ts (Version2)>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { HeroService }     from './hero.service';
@Component({
    selector: 'my-app',
    template: `
    <h1>{{title}}</h1>
    <a [routerLink]="['/heroes']">Heroes</a>
    <router-outlet></router-outlet>
  `,
    directives: [ROUTER_DIRECTIVES],
    providers: [
        HeroService
    ]
})
export class AppComponent {
    title = 'Tour of Heroes';
}
cs



 <router-outlet> 

우리는 템플릿의 하단에 <router-outlet> marker tag를 추가하여 위치를 알려야 한다. RouterOutlet 은 Router_DIRECTIVES 중 하나이다. 라우터는 각각의 컴포넌트를 <router-outlet>하단에 즉시 표시한다.


 <routerLink

Notice the [routerLink] binding in the anchor tag. We bind the RouterLink directive (another of the ROUTER_DIRECTIVES) to an array that tells the router where to navigate when the user clicks the link.



7. 에러사항



Exception : Template parse errors: Can't bind to 'router-link' since it isn't a known native property


app.component.ts의 template 부분에서 에러가 난다. 컴포넌트에 directives 추가 안해서.. (올바르게 포스팅 수정함)




여기까지하면 Title만 보이고 Heroes List가 보이지 않을 것이다.


6. Routing (2) - 대시보드 화면 만들기

1. Add a Dashboard



Routing은 단지 우리가 여러 화면을 보고 있다고 생각하게 만든다.



<app.routes.ts 수정>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const routes: RouterConfig = [
    {
        path: 'heroes',
        component: HeroesComponent
    },
    {
        path:'dashboard',
        component:DashboardComponent
    },
    {
        path:'',
        redirectTo:'/dashboard',
        pathMatch:'full'
    }
];
cs




< app.component.ts 의 template 수정>

1
2
3
4
5
6
7
8
  template: `
  <h1>{{title}}</h1>
  <nav>
  <a [routerLink]="['/dashboard']" routerLinkActive="active"> Dashboard</a>
  <a [routerLink]="['/heroes']" routerLinkActive="active">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
`,
cs




브라우저를 새로고침하면 대시보드와 히어로를 화면이동 할 수 있게 된다.



2. Dashboard Top Heroes



template: '<h3>My Dashboard</h3>'

dashboard.component.ts의 template 부분을 아래와 같이 변경

templateUrl: 'app/dashboard.component.html'



<dashboard.component.ts>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>Top Heroes</h3>
    <div class="grid grid-pad">
        <div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
            <div class="module hero">
                <h4>{{hero.name}}</h4>
            </div>
        </div>
    </div>
 
</body>
</html>
cs



<dashboard.component.ts>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component, OnInit } from '@angular/core';
 
import { Hero } from './hero';
import { HeroService } from './hero.service';
 
@Component({
    selector: 'my-dashboard',
    templateUrl: 'app/dashboard.component.html'
})
export class DashboardComponent implements OnInit{
    heroes:Hero[] =[];
 
    constructor(private heroService: HeroService){}
 
    ngOnInit(){
        this.heroService.getHeroes().then(heroes=>this.heroes=heroes.slice(1,5));
    }
 
    gotoDetail(){
        /* not implemented yet */
    }
}
cs

 

[line 11] heroes 배열 생성

[line 13] 생성자에 HeroService 삽입하고 heroService라는 private field로 만듬.

[line 16] heroes를 획득하기 위하여 service 호출



3. Navigate to Hero Details



<app.routes.ts 에 추가>

1
2
3
4
5
6
import {HeroDetailComponent} from "./hero-detail.component";
 
{
    path: 'detail/:id',
    component:HeroDetailComponent
}
cs



<hero-detail.component.ts>


우리는 더이상 부모 컴포넌트에게서 hero를 받지 않아도 된다. HeroDetailComponent가 ActivateRoute 서비스의 식별가능한 param인 id를 가져와서 HeroService의 hero 를 사용한다.



- ActivatedRoute를 사용하기 위한 import

import { ActivatedRoute } from '@angular/router';

- hero를 가져오기 위한 import


import { HeroService } from './hero.service';

- ngOnInit, ngOnDestroy를 사용하기 위한 import

- ngOnInit 컴포넌트 내부에서 HeroService를 호출하고, ngOnDestroy를 통해 param의 유효기간을 정리

[관련내용] App Lifecycle


import { Component, OnInit, OnDestroy } from '@angular/core';



4. Select a Dashboard Hero



<dashboard.component.ts 의 gotoDetail()>

gotoDetail(hero: Hero){
let link=['/detail',hero.id];
this.router.navigate(link);
}


gotoDetail()의 navigate 방법

1. set a route link parameters array

2. pass the array to the router's navigate method.



이것 까지하면

Dashboard 와 Heroes의 화면 분리

Dashboard에서 히어로의 이름을 클릭하면 바로 히어로의 디테일이 보임




5. 중간 정리



<app.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES } from '@angular/router';
import { HeroService }     from './hero.service';
@Component({
    selector: 'my-app',
    template: `
    <h1>{{title}}</h1>
    <nav>
    <a [routerLink]="['/dashboard']" routerLinkActive="active"> Dashboard</a>
    <a [routerLink]="['/heroes']" routerLinkActive="active">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  `,
    directives: [ROUTER_DIRECTIVES],
    providers: [
        HeroService
    ]
})
export class AppComponent {
    title = 'Tour of Heroes';
}
cs



<app.routes.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { provideRouter, RouterConfig }  from '@angular/router';
import { HeroesComponent } from './heroes.component';
import {DashboardComponent} from "./dashboard.component";
import {HeroDetailComponent} from "./hero-detail.component";
 
const routes: RouterConfig = [
    {
        path: 'heroes',
        component: HeroesComponent
    },
    {
        path:'dashboard',
        component:DashboardComponent
    },
    {
        path:'',
        redirectTo:'/dashboard',
        pathMatch:'full'
    },
    {
        path: 'detail/:id',
        component:HeroDetailComponent
    }
];
 
export const appRouterProviders = [
    provideRouter(routes)
];
cs



<dashboard.component.html>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>Top Heroes</h3>
    <div class="grid grid-pad">
        <div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1-4">
            <div class="module hero">
                <h4>{{hero.name}}</h4>
            </div>
        </div>
    </div>
 
</body>
</html>
cs



<dashboard.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Component, OnInit } from '@angular/core';
import { Router} from '@angular/router';
 
import { Hero } from './hero';
import { HeroService } from './hero.service';
 
@Component({
    selector: 'my-dashboard',
    templateUrl: 'app/dashboard.component.html'
})
export class DashboardComponent implements OnInit{
    heroes:Hero[] =[];
 
    constructor(
        private router: Router,
        private heroService: HeroService) {
    }
 
    ngOnInit(){
        this.heroService.getHeroes().then(heroes=>this.heroes=heroes.slice(1,5));
    }
 
    gotoDetail(hero: Hero){
        let link=['/detail',hero.id];
        this.router.navigate(link);
    }
}
cs



<hero.service.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {
    getHeroes() {
        return Promise.resolve(HEROES);
    }
 
    getHero(id: number){
        return this.getHeroes().then(heroes=>heroes.find(hero=>hero.id===id));
    }
}
cs



<hero.ts>

export class Hero {
id: number;
name: string;
}



<hero-detail.component.html>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div *ngIf="hero">
        <h2>{{hero.name}} details!</h2>
     <div>
         <label>id: </label>{{hero.id}}</div>
      <div>
         <label>name: </label>
         <input [(ngModel)]="hero.name" placeholder="name" />
     </div>
     <button (click)="goBack()">Back</button>
    </div>
 
</body>
</html>
cs



<hero-detail.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
 
import { Hero } from './hero';
import { HeroService } from './hero.service';
 
@Component({
    selector: 'my-hero-detail',
    templateUrl: 'app/hero-detail.component.html',
})
export class HeroDetailComponent implements OnInit, OnDestroy {
    hero: Hero;
    sub: any;
 
    constructor(
        private heroService: HeroService,
        private route: ActivatedRoute) {
    }
 
    ngOnInit() {
        this.sub = this.route.params.subscribe(params => {
            let id = +params['id'];
            this.heroService.getHero(id)
                .then(hero => this.hero = hero);
        });
    }
 
    ngOnDestroy() {
        this.sub.unsubscribe();
    }
 
    goBack() {
        window.history.back();
    }
}
cs



<heroes.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
 
 
@Component({
    selector: 'my-heroes',
    template:
    '<h1>{{title}}</h1>'+
    '<h2>My Heroes</h2>'+
    '<ul class="heroes">'+
    '  <li *ngFor="let hero of heroes"'+
    '    [class.selected]="hero === selectedHero"'+
    '    (click)="onSelect(hero)">'+
    '    <span class="badge">{{hero.id}}</span> {{hero.name}}'+
    '  </li>'+
    '</ul>'+
    '<my-hero-detail [hero]="selectedHero"></my-hero-detail>',
    directives:[HeroDetailComponent],
    providers:[HeroService]
})
 
export class HeroesComponent implements OnInit {
    title = 'Tour of Heroes';
    heroes: Hero[];
    selectedHero: Hero;
    constructor(private heroService: HeroService) { }
    getHeroes() {
        this.heroService.getHeroes().then(heroes => this.heroes = heroes);
    }
    ngOnInit() {
        this.getHeroes();
    }
    onSelect(hero: Hero) { this.selectedHero = hero; }
}
cs



<main.ts>

import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';

bootstrap(AppComponent,[
appRouterProviders
]).catch(err => console.error(err));



<mock-heroes.ts>

import { Hero } from './hero';
export const HEROES: Hero[] = [
{id: 11, name: 'Mr. Nice'},
{id: 12, name: 'Narco'},
{id: 13, name: 'Bombasto'},
{id: 14, name: 'Celeritas'},
{id: 15, name: 'Magneta'},
{id: 16, name: 'RubberMan'},
{id: 17, name: 'Dynama'},
{id: 18, name: 'Dr IQ'},
{id: 19, name: 'Magma'},
{id: 20, name: 'Tornado'}
];




6. Select a Hero in the HeroesComponent


 우리는 더이상 HeroDetailComponent의 전체를 heroes.component.ts에서 보여줄 필요가 없어졌다. 히어로의 디테일은 대시보드 화면에서 보여질 것이다. 그러나 다양성을 위하여 살짝 꼬아보겠다. 사용자가 리스트의 히어로를 클릭할 때 full detail page로 이동하는 대신에 간단한 그 페이지에서 mini-detail을 보여주자.


<heros-detail.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    template:`
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    
    <div *ngIf="selectedHero">
        <h2> {{selectedHero.name|uppercase}} is my hero</h2>
        <button (click)="gotoDetail()"> View Details</button>
    </div>
`,
cs


template이 너무 길어지니 html 파일로 분리하고 HeroComponent의 페이지 이동을 추가하자.


<heroes.component.ts>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Component, OnInit } from '@angular/core';
import { Router} from '@angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';
 
 
@Component({
    selector: 'my-heroes',
    templateUrl: 'app/heroes.component.html',
    styleUrls:['app/heroes.component.css']
})
 
export class HeroesComponent implements OnInit {
    heroes: Hero[];
    selectedHero: Hero;
    constructor(
        private router: Router,
        private heroService: HeroService) { }
    getHeroes() {
        this.heroService.getHeroes().then(heroes => this.heroes = heroes);
    }
    ngOnInit() {
        this.getHeroes();
    }
    onSelect(hero: Hero) { this.selectedHero = hero; }
    gotoDetail() {
        this.router.navigate(['/detail'this.selectedHero.id]);
    }
}
cs




7. Styling the App



AngularJS의 디자인팀에서 CSS를 제공해주었다. 사진을 클릭하면 CSS 제공 페이지로 이동하며 Ctrl + F 에서 Styling the App을 검색하면 CSS 부분 찾기 수월 할 것이다.







반응형