반응형
SMALL
두 번째 시간입니다.
이제는 좀더 Angular 프레임워크 사용에 복잡해지는 부분이 많을거 같기도 하네요.
그래도 반복만이 살 길 !
계속해서 해보도록 합시다.
컴포넌트 만들기
컴포넌트 생성
ng generate component hero-detail
- src/app/hero-detail 폴더를 생성하고 그 안에 아래의 파일을 생성합니다.
- 컴포넌트 스타일을 지정하는 CSS 파일
- 컴포넌트 템플릿을 정의하는 HTML 파일
- 컴포넌트 클래스 HeroDetailComponent가 정의된 TypeScript ㅏ일
- HeroDetailComponent 클래스 파일을 테스트하는 파일
- 그리고 자동으로 해당 컴포넌트는 app.module.ts 파일에 @NgModule에 등록됩니다.
@Input() 히어로 프로퍼티 추가하기
- 외부 컴포넌트에서 바인딩되어 프로퍼티 값을 전달 받을 때, 전달 받은 곳에서 해당 프로퍼티를 @Input() 데코레이터를 사용해서 입력 프로퍼티로 선언해야합니다.
import { Component, OnInit, Input } from '@angular/core';
...
...
@Input() hero: Hero;
...
...
- 외부 컴포넌트에서는 아래와 같이 바인딩을 시켜줘야합니다.
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
- [hero]="selectedHero"의 경우엔 Angular가 제공하는 프로퍼티 바인딩 문법입니다.
- selectedHero 프로퍼티를 app-hero-detail에게 hero라는 프로퍼티로 단방향 데이터 바인딩을 시킨겁니다.
서비스 추가하기
- 컴포넌트는 데이터를 저장하는 위치가 아닙니다. 표시하는 곳이죠. 그래서 데이터 처리 로직은 서비스에게 맡기는 것이 좋습니다.
ng generate service hero
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
getHeroes(): Hero[] {
return HEROES;
}
}
- Angular CLI로 만든 서비스 클래스는 Injectable이 데코레이터로 사용됩니다. 이는 의존성 주입 시스템에 포함되는 클래스라고 선언하는 구문입니다.
- @Component() 데코레이터에 메타데이터를 사용했던 것과 같은 방식입니다.
- 위와 같은 방식으로 데이터를 service에 집중하면 컴포넌트는 데이터와 구분되어 좀 더 캡슐화된 기능을 사용할 수 있습니다.
angular.kr/tutorial/toh-pt4#heroservice-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0
- 프로바이더의 경우엔 이해하기 힘들기에... url을 두고 반복해서 읽어보도록 합시다.
- 결과적으로는 어떤 클래스를 데이터 베이스화 할거냐.. 이런 의미 같습니다.
HeroService 주입하기
import { HeroService } from '../hero.service';
constructor(private heroService: HeroService) {}
- 이렇게 작성하면 heroService 인자를 클래스 프로퍼티로 선언하면서 HeroService 타입의 객체가 들어가게 됩니다.
// 초기화 코드이기 때문에 ngOnInit() 라이프사이클 때 호출하는 것이 좋습니다.
// 생성자에서는 클래스 프로퍼티를 연결하는 정도로 가볍게 유지하는 것이 좋습니다.
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
- 위에 private으로 지정한 heroService 프로퍼티에서 Service에서 선언한 메소드를 선언할 수 있습니다.
옵저버블 데이터
- 만약, 서버와의 통신으로 이루어진 getHeroes() 메소드였다면, 해당 메소드는 비동기 처리로 동작해야합니다.
- 콜백 함수, Promise, Observable 방법이 있는데, Angular에서는 Observable 방법을 사용합니다.
- Angular가 제공하는 HttpClient.get 메소드는 Observable을 반환하기 때문에 이를 사용합시다.
옵저버블 HeroService
import { Observable, of } from 'rxjs';
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
- RxJS 라이브러리가 제공하는 Observable은 가장 중요한 클래스입니다. Angular의 HttpClient 클래스가 제공하는 모든 메소드는 RxJS가 제공하는 Observable 타입을 반환합니다.
구독하기
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
- Observable.subscribe()를 사용한 부분이 가장 중요한데, 이 전에는 직접 heroes 프로퍼티에 할당했는데, 이 동작은 동기 방식으로 동작하기 때문에 서비스가 데이터를 즉시 반환하거나 서버의 응답이 동기 방식으로 전달될 때에만 제대로 동작합니다.
- 하지만 서버와 통신하는 경우에는 이 로직이 제대로 동작하지 않습니다.
- Observable의 경우엔 언제 응답이 도착하는지 관계없이 subscribe가 서버에서 받은 응답을 콜백 함수로 전달하고, 이를 heroes 프로퍼티에 할당합니다.
정리
- 컴포넌트가 데이터를 직접 가져오는 방식을 service 클래스를 이용하여 변경했습니다.
- 프로바이더를 사용해서 HeroService를 최상위 인젝터에 등록했습니다.
- HeroService를 컴포넌트에 의존성으로 주입하기 위해 Angular의 의존성 주입을 사용했습니다.
- HeroService에서 비동기 방식으로 데이터를 가져오는 메소드를 구현했습니다.
- Observable<Hero[]>를 반환할 때 RxJS가 제공하는 of() 함수를 사용했습니다.
- 초기화 함수를 컴포넌트 생성자가 아니라 ngOnInit 라이프사이클 후킹 함수에 구현했습니다.
- 서비스를 컴포넌트에 의존성으로 주입할 수 있지만, 다른 서비스에도 의존성 주입을 할 수 있습니다.
네비게이션 추가하기
// flat 옵션은 새로운 폴더를 만들지 않고 src/app 폴더에 파일을 생성합니다.
// --module=app 옵션을 사용하면 AppModule의 imports 배열에 라우팅 모듈을 자동으로 추가합니다.
ng generate module app-routing --flat --module=app
- Angular에서는 최상위 모듈과 같은 계층에 별개의 모듈을 두고 최상위 라우팅 모듈을 정의하는 방법을 권장합니다. AppModule은 이렇게 정의한 라우팅 설정을 로드해서 사용하면 됩니다.
- 일반적으로 최상위 라우팅 모듈 이름을 AppRoutingModule이라고 정의합니다.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
// 아래의 기본 라우팅 규칙을 통해서 해당 주소로 이동하게 됩니다.
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'heroes', component: HeroesComponent },
// 아래와 같이 정의하면 라우팅 변수를 지정할 수 있습니다.
{ path: 'detail/:id', component: HeroDetailComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
- RouterModule과 Routes 심볼을 로드하고, HeroesComponent를 로드해서 이동할 수 있도록 합니다.
- 라우팅 규칙이 있습니다.
- path: 브라우저 주소표시줄에 있는 URL과 매칭될 문자열을 지정합니다.
- component: 라우터가 생성하고 화면에 표시할 컴포넌트를 지정합니다.
- @Ngmodule에 메타데이터를 지정하면 모듈이 생성될 때 라우터를 초기화하면서 주소가 변화되는 것을 감지합니다. 그래서 라우터를 초기화 하기 위해서 routes라는 인자를 forRoot() 메소드에 넣습니다.
- 그리고 App에서도 RouterModule을 사용할 수 있도록 exports 배열에 추가합니다.
<h1>{{ title }}</h1>
<nav>
<a routerLink="heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
- <router-outlet>은 라우팅 된 화면이 표시될 위치를 지정하는 엘리먼트입니다.
- routerLink는 RouterModule이 제공하는 기능이며, 이 기능이 적용된 엘리먼트를 클릭하면 네비게이션을 실행합니다.
- 아래와 같이 라우팅 규칙을 추가할 수 있습니다.
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{ hero.id }}">
</a>
라우팅 변수 추출
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
constructor(
private route: ActivateRoute,
private heroService: HeroService,
private location: Location
) {}
getHero(): void {
// route.snapshot은 라우팅 규칙에 대한 정보를 담고 있는 객체입니다.
// paramMap을 통해 라우팅 변수를 참조할 수 있습니다.
// +를 붙인 이유는 문자열로 받아온 라우팅 변수를 숫자로 변환하기 위해서 입니다.
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
// 이전에 주입 받은 Location 서비스를 사용하여 이전 화면으로 돌아갈 수 있습니다.
this.location.back();
}
- ActivatedRoute는 라우팅 규칙에 대한 정보를 담고 있어서 이를 참조하면 URL을 통해 컴포넌트로 전달되는 변수를 추출할 수 있습니다.
정리
- 화면에 표시하는 컴포넌트를 전환하기 위해 Angular 라우터를 추가했습니다.
- AppComponent에 <a> 링크와 <router-outlet>을 추가하면 네비게이션 동작을 실행할 수 있습니다.
- 라우터 설정은 AppRoutingModule에 정의합니다.
- 간단한 라우팅 규칙부터 리다이렉트 라우팅 규칙, 라우팅 변수가 있는 라우팅 규칙을 정의했습니다.
- 앵커 엘리먼트에 routerLink 디렉티브를 적용했습니다.
이상 Angular Tutorial with Docs - 2였습니다. ^_^
반응형
LIST
'Web + APP > Angular' 카테고리의 다른 글
RxJS가 해결하려고 했던 문제 1 (0) | 2021.05.19 |
---|---|
RxJS를 시작하기 전에 (0) | 2021.05.19 |
forRoot() / forChild() 당신은 누구신가요? (11) | 2021.05.13 |
Angular Large List 최적화 (Angular CDK) (0) | 2021.04.20 |
Angular Tutorial with Docs - 1 (3) | 2021.04.16 |