29
Http Client Routing & Navigation - 권권권 -

Angular2 router&http

Embed Size (px)

Citation preview

Http ClientRouting & Navigation

- 권동준 -

Http1. Http 요청은 기본적으로 XMLHttpRequest 를 기반으로 사용한다 .

2. Http 는 injectable 클래스로 Http 요청 시 사용 .

3. Promise 과 Observable(Rxjs) 으로 사용할 수 있지만 Angular2.0 에서는 기본적으로 Http 요청 후 응답이 왔을 시 Observable 를 리턴 .

4.Promise 를 사용할 경우 angular2 http 에서는 Observable 반환 하기 때문에 새로운 promise 로 선언 후 반환해야 된다 .( 자세한 설명은 예제를 보며 ..)

5. Rx.js 를 알면 편하고 나아가서 반응형 프로그래밍을 이해하면 더욱 좋음 .

HttpHttp 를 사용하려면 HTTP_PROVIDERS 의존성 주입 (DI)(Http 는 injectable 클래스 )Html 에도 선언

providers: [ /* HTTP_PROVIDERS 의존성 주입 */ HTTP_PROVIDERS, /* HeroService 의존성 주입 */ HeroService, // in-memory web api providers provide(XHRBackend, { useClass: InMemoryBackendService }), // in-mem server provide(SEED_DATA, { useClass: HeroData }) // in-mem server data]

<script src="node_modules/angular2/bundles/http.dev.js"></script>

Http @Injectable()export class HeroService { constructor (private http: Http){}

private _heroesUrl = 'app/heroes'; getHeroes (): Observable<Hero[]> { return this.http.get(this._heroesUrl).map(this.extractData).catch(this.handleError); } private extractData(res: Response): Hero[] { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body: any = res.json(); return body.data || []; } private handleError (error: any) { let errMsg = error.message || 'Server error'; console.error(errMsg); return Observable.throw(errMsg); }}

꼭 response 개체를 잘 사용하기 위해서는2 가지를 해야한다 .

1. check for a bad response2. parse the response data into a JSON object

response 개체 자체를 리턴하는 것은 안좋다 !

server 의 정보를 숨기고 필요한 정보만을return 하는 것이 좋으며 사용자 입장에서도

그 데이터만 필요로 하기 때문이다 . 어디서 무엇을 하는지는 굳이 알 필요가 없다 . getHeroes() {

this._heroService.getHeroes() .subscribe(heroes => this.heroes = heroes, error => this.errorMessage = <any>error);}

HttpaddHero (name: string) { if (!name) {return;} this._heroService.addHero(name) .subscribe( hero => this.heroes.push(hero), error => this.errorMessage = <any>error);}

@Injectable()export class HeroService { constructor (private http: Http){} private _heroesUrl = 'app/heroes'; addHero (name: string): Observable<Hero> { let body = JSON.stringify({ name }); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this._heroesUrl, body, options) .map(this.extractData) .catch(this.handleError); } private extractData(res: Response): Hero[] { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); }

let body: any = res.json(); return body.data || []; } private handleError (error: any) { let errMsg = error.message || 'Server error'; console.log('Error Start'); console.error(errMsg); return Observable.throw(errMsg); }}

Header 정보 등등은BaseRequestOptions 사용하면 미리 지정 가능

Http catch 는 try-catch 의 catch라고 생각하면 이해가 빠르다 .

map 안에서 throw new Error 을 하면 catch 에서 선언된 handleError 을 실행한다 .

@Injectable()export class HeroService { constructor (private http: Http){}

private _heroesUrl = 'app/heroes'; getHeroes (): Observable<Hero[]> { return this.http.get(this._heroesUrl).map(this.extractData).catch(this.handleError); } private extractData(res: Response): Hero[] { if (res.status < 200 || res.status >= 300) { throw new Error('Bad response status: ' + res.status); } let body: any = res.json(); return body.data || []; } private handleError (error: any) { let errMsg = error.message || 'Server error'; console.error(errMsg); return Observable.throw(errMsg); }}

Http 1. get(url: string, options?: RequestOptionsArgs) : Observable<Response>

2. post(url: string, body: string, options?: RequestOptionsArgs) : Observable<Response>

3. put(url: string, body: string, options?: RequestOptionsArgs) : Observable<Response>

4. delete(url: string, options?: RequestOptionsArgs) : Observable<Response>

Jsonp1. JSONP 는 HTTP 서비스의 확장이며 , GET 요청에 제한을 둔다 .

2. JSONP 는 읽기 전용이다 .

3. JSONP_PROVIDERS 로 의존성 주입을 한다 .

4. JSONP 역시 injectable 클래스

5. Promise 과 Observable(Rxjs) 으로 사용할 수 있지만 Angular2.0 에서는 기본적으로 Http 요청 후 응답이 왔을 시 Observable 를 리턴 .

Jsonp1. Jsonp 를 사용하려면 JSONP_PROVIDERS 의존성 주입 (DI)(JSONP 는 injectable 클래스 )

@Component({ selector: 'my-wiki', providers:[JSONP_PROVIDERS, WikipediaService]})

Jsonp@Injectable()export class WikipediaService { constructor(private jsonp: Jsonp) {}

search (term: string) { let wikiUrl = 'http://en.wikipedia.org/w/api.php';

var params = new URLSearchParams(); params.set('search', term); // the user's search value params.set('action', 'opensearch'); params.set('format', 'json'); params.set('callback', 'JSONP_CALLBACK');

return this.jsonp .get(wikiUrl, { search: params }) .map(request => <string[]> request.json()[1]); }}

@Component({ selector: 'my-wiki', template: ` <h1>Wikipedia Demo</h1> <p><i>Fetches after each keystroke</i></p>

<input #term (keyup)="search(term.value)"/> <ul> <li *ngFor="#item of items | async">{{item}}</li> </ul> `, providers:[JSONP_PROVIDERS, WikipediaService]})export class WikiComponent {

constructor (private _wikipediaService: WikipediaService) {}

items: Observable<string[]>;

search (term: string) { this.items = this._wikipediaService.search(term); }}

Promise & ObservableAngular2.0 Http 는 Observable 개체를 return 한다 . 하지만 Promise 도 사용할 수 있게 제공한다 .

/* Observable */return this.http.get(this._heroesUrl).map(this.extractData).catch(this.handleError);

/* Promise */return this.http.get(this._heroesUrl) .toPromise().then(this.extractData).catch(this.handleError);

/* Observable */this._heroService.getHeroes().subscribe(heroes => this.heroes = heroes, error => this.errorMessage = <any>error);/* Promise */this._heroService.getHeroes().then(heroes => this.heroes = heroes, error => this.errorMessage = <any>error);

Promise or Observableconst Rx = require('rx');

const promise = new Promise((resolve) => { setTimeout(() => { console.log('1 - 이것도 실행 ?'); resolve(100) }, 500);

console.log('promise 시작 ');});

// promise.then(x => console.log(x));

const observable = Rx.Observable.create((observer) => { setTimeout(() => { console.log('2 - 이것도 실행 ?'); observer.onNext(100); }, 500);

console.log('observable 시작 ');});

// observable.forEach(x => console.log(x));

차이점 보기!

Routing & Navigation1. Routing 의 URL 은 기본적으로 HTML5 pushState 스타일이다 .

2. 자식과 부모 라우터를 구성할 수 있다 .

3. Router Life Cycle Hooks 가 존재한다 .(3 가지 !)

4. 자식과 부모 사이의 query parameter 은 matrix URL 방식이다 .

Routing & NavigationHtml 추가 및 ROUTER_PROVIDERS 의존성 주입 (DI)

import {ROUTER_PROVIDERS} from "angular2/router";import {AppComponent} from "./component/app.component";import {bootstrap} from "angular2/platform/browser";

bootstrap(AppComponent, [ROUTER_PROVIDERS]);

<script src="node_modules/angular2/bundles/router.dev.js"></script>

<base href="/">

HTML5 pushState Style

Routing & Navigation/** * 라우터 설정 */@RouteConfig([ { /* 자식과 부모와의 관계이므로 기본 path 이후는 ... 으로 표시 */ path: '/crisis-center/...', /* 중복이 되면 안되며 , PascalCase 방식으로 네이밍을 해야된다 . */ name: 'CrisisCenter', component: CrisisCenterComponent, /* 해당 url 에 어떠한 정보가 없으면 아래의 useAsDefault 로 선언된 라우터로 이동된다 . (localhost/ 들어오면 정보가 없기 때문에 아래의 정보가 있는 곳으로 이동 ) */ useAsDefault: true }, {path: '/heroes', name: 'Heroes', component: HeroListComponent}, {path: '/hero/:id', name: 'HeroDetail', component: HeroDetailComponent},

/* 할당된 url 이 없을시 아래와 같이 지정한다 . 물론 404 Component를 만들어서 넣어도 된다 . */ { path: '/**', redirectTo: ['CrisisCenter'] }])

Routing & Navigation@Component({ selector: 'my-app', template: ` <h1 class="title">Component Router</h1> <nav> <a [routerLink]="['CrisisCenter']">Crisis Center</a> <!-- 부모의 라우터에서 자식의 라우터중 어디로 갈지 배열로 선언해준다 . --> <a [routerLink]="['CrisisCenter', 'CrisisList']">Crisis Center-list</a> <a [routerLink]="['Heroes']">Heroes</a> <!-- parmater 값은 name 배열 다음에 json 형식으로 넣어준다 . --> <a [routerLink]="['HeroDetail', {id: 11}]">11. Hero Detail</a> </nav> <!-- 라우터에 따라 라우팅 되는 곳 --> <router-outlet></router-outlet> `, providers: [DialogService, HeroService], /* 이게 선언이 되여 RouterOutlet, RouterLink을 사용할 수 있다 . */ directives: [ROUTER_DIRECTIVES]})

Routing & NavigationRouting 방식 1. URL 변경 방식 1) URL 이 변경이 되면 Router 의 Path 를 찾아 매칭 . 2) component instance 를 생성해주거나 검색 . 3) view 에 표시해준다 .

2. 이름 방식으로 route 호출 1) 해당 이름의 path 로 경로를 구성하고 주소위치랑 히스토리를 업데이트 . 2) component instance 를 생성해주거나 검색 . 3) view 에 표시해준다 .

Routing & Navigation// AppComponent (부모 )@RouteConfig([ { /* 자식과 부모와의 관계이므로 기본 path 이후는 …으로 표시 */ path: ' /crisis-center/… ' , name: ' CrisisCenter ' , component: CrisisCenterComponent, useAsDefault: true }])

부모 Component 의 Template 에 아래와 같이 선언되어야한다 .

<router-outlet></router-outlet>

자식 CrisisCenterComponent 의 Template 에 아래와 같이 선언되어야한다 .

<router-outlet></router-outlet>

// CrisisCenterComponent (자식 )@RouteConfig([ { path:'/', name: 'CrisisList', component: CrisisListComponent, useAsDefault: true }, { path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent }])

자식과 부모 사이의 query parameter 은 matrix URL 방식이다 .localhost/crisi-center/;id=1;foo=foo

Routing & Navigationexport class HeroListComponent implements OnInit { heroes: Hero[];

private _selectedId: number;

constructor( private _service: HeroService, private _router: Router, routeParams: RouteParams) { /* routeParams 를 이용하여 parameter 값을 가지고 올수 있다 . url/query param 전부 가지고 온다 . */ this._selectedId = +routeParams.get('id'); }

isSelected(hero: Hero) { return hero.id === this._selectedId; }

onSelect(hero: Hero) { this._router.navigate( ['HeroDetail', { id: hero.id }] ); }

ngOnInit() { this._service.getHeroes().then(heroes => this.heroes = heroes) }}

Routing & Navigationconstructor( routeParams: RouteParams) { this._selectedId = +routeParams.get('id');}

ngOnInit() { this._selectedId =+ _routeParams.get('id');}

VS

2 가지의 차이점은 결국 시점 차이다 . ngOnInit 은 구성요소가 초기화 될때 호출되고 , constructor 는 구성요소가 구성될때 실행된다 .

결론적으로 constructor 먼저 실행되고 ngOnInit 이 호출된다 . Angular 에서는 테스트때문에constructor 보다는 ngOnInit 방식 ( 자스민에서 테스트가 가능하다 ) 으로 하는걸 추천한다 . 하지만

굳이 테스트가 필요하지 않은 것 혹은 해당 시점에 맞게 프로그래밍 할 때는 다른 방식을 해도 된다 .

Routing & NavigationRouter Life Cycle Hooks 1. CanActivate

2. OnActivate

3. CanDeactivate

이 3 가지의 Hooks 는 각각의 시점이 서로 다르고 사용용도도 다르다 .

Routing & Navigation@CanActivate((next, prev) => { /* next 는 이동하려는 Route, prev 는 현재 라우터 */ console.log(`next = ${next.urlPath} // now = ${prev ? prev.urlPath : null}`); /* false 이면 routing 이 취소된다 . Promise 를 통해서 true 혹은 false 를 줄수 있다 . */ return new Promise(resolve => { setTimeout(() => resolve(true), 10000); });})

@CanActivate1. 시점 : Routing 이 되기 직전 .(angular1 의 resolve)2. 용도 : Login/Auth 체크시 많이 사용된다 .3. return : true 이면 Routing 이 되며 , false 이면 Routing 이

취소된다 .

Routing & NavigationrouterOnActivate(next: ComponentInstruction, prev: ComponentInstruction) { this.log = `Finished navigating from "${prev ? prev.urlPath : 'null'}" to "${next.urlPath}"`; console.log(this.log); /* Routing이 완료되어 구성되자마자 실행이 된다 . */ return new Promise(resolve => { setTimeout(() => resolve(null), 1000); });}

onActivate1. 시점 : Routing 완료되어 구성되자마자 실행2. return : promise 를 통해서 리턴이되며 해당 promise 가

될때까지 기다린다 .

Routing & NavigationrouterCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) : any { if (!this.crisis || this.crisis.name === this.editName) { return true; } return this._dialog.confirm('Discard changes?');}

onActivate1. 시점 : 해당 Route 에서 다른 Route 로 이동할 때 된다 .( 이동하기

바로전 )2. return : true 이면 이동이되며 , false 면 이동이 안된다 .

Routing & Navigation@CanActivate((next, prev) => { console.log(`CanActivate`); return true})export class CrisisDetailComponent implements OnInit, CanDeactivate { constructor() { console.log(`constructor`); } ngOnInit() { console.log(`ngOnInit`);

} routerOnActivate(next: ComponentInstruction, prev: ComponentInstruction) { console.log(`OnActivate`); /* Routing 이 완료되어 구성되자마자 실행이 된다 . */ return new Promise(resolve => { setTimeout(() => resolve(null), 1000); }); }

routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) : any { if (!this.crisis || this.crisis.name === this.editName) { return true; } return this._dialog.confirm('Discard changes?'); }}

출력 되는 순서1. CanActivate 2. constructor3. OnActivate4. ngOnInit

Routing & Navigation

해당 라우터로 이동시에<a [routerLink]="['CrisisCenter']">

위와 같이 선언이 되어 있는 부분에 해당 라우터를 호출을 하면 아래와 같이 자동으로 class 를 부여한다 .

<a href="/crisis-center" class="router-link-active">

Routing & Navigation

상세

아래와 같이 부모 자식간의 관계가 아닌 같은 노드의 다른 라우터이기 때문에 heroes 의 상세에 들어가도 Heroes 에 class 가 부여되지

않는다 . 서로 다른 라우터로 인식하기 때문이다 .{path: '/heroes', name: 'Heroes', component: HeroListComponent},{path: '/hero/:id', name: 'HeroDetail', component: HeroDetailComponent},

Routing & Navigation

상세

아래와 같이 부모와 자식관계이기 때문에 class 가 부여된다 .부모{ path: '/crisis-center/...', name: 'CrisisCenter', component: CrisisCenterComponent, useAsDefault: true}

자식{path:'/', name: 'CrisisList', component: CrisisListComponent, useAsDefault: true},{path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent}

예제 소스Http

- https://github.com/mayajuni/angular2-http

Routing & Nav

- https://github.com/mayajuni/angular2-routing-nav