Angular CodeLab 두번째

이상훈
상훈 Devlog
Published in
10 min readOct 22, 2018

Angular 공식 홈페이지에서 제공하는 튜토리얼을 번역하고 정리한 글이다. 원문은 https://angular.io/docs에서 확인할 수 있다.

CodeLab 자료

보이지 않는다면 “SHOW EMBED”를 클릭!

순서

Angular Codelab 첫번째

  • 1. Introduction
  • 2. The Application Shell
  • 3. The Hero Editor
  • 4. Displaying a List
  • 5. Master/Detail Components

Angular Codelab 두번째

  • 6. Services
  • 7. Routing
  • 8. HTTP

6. Service

왜 서비스를 사용해야 하는가

컴포넌트가 뷰를 전시하는 일 외에 데이터를 직접 가져오거나 저장하는 등의 일은 하지 않는 것이 좋다. 관심사를 분리하고 재사용성을 늘리려면 컴포넌트는 데이터의 표현에 집중하고, 데이터에 접근하는 것은 서비스에 위임하는 것이 좋다.

HeroService 생성

Angular CLI를 사용하여 hero 서비스를 생성한다.

ng generate service hero

다음과 같은 스켈레톤 코드를 확인할 수 있다.

@Injectable()

위 코드를 보면 새로운 데코레이터가 있는 것을 알 수 있다. 이것은 해당 클래스를 Angular의 의존성 주입 시스템에 참여하도록 하는 데코레이터이다. 또한 providedIn 속성으로 root 값을 주게 되면 어플리케이션 전역에서 이 서비스를 의존성 주입할 수 있게 된다.

서비스에 히어로 목록을 반환하는 기능을 구현해보자. 다음과 같이 HeroHEROESimport하고 getHeroes 메소드를 구현한다.

HeroesComponent 수정

HeroServiceimport하고 주입한다. constructorHeroService 타입의 heroService 인자를 private 접근제한자로 추가한다. 이를 통해 private heroService 멤버 변수를 정의하고 HeroService를 주입하게 된다.

constructor(private heroService: HeroService) { }

Angular 는 HeroesComponent를 만들면 의존성 주입 시스템은 heroService 인자를 HeroService의 싱글턴 인스턴스로 설정한다.

그 다음으로는 히어로 목록을 가져오는 메소드를 생성하고 이것을 ngOnInit에서 호출하도록 한다.

물론 생성자에서 getHeroes() 메소드를 호출할 수도 있지만 좋은 방법은 아니다. 생성자는 타입스크립트 상에서 클래스 인스턴스를 생성 시에 사용하고 데이터를 초기화하거난 설정값을 가져오는 등의 행위는 ngOnInit 생명주기에서 하는 것이 좋다.

Observable data

지금까지는 HeroServicegetHeroes() 메소드는 히어로 목록을 동기적으로 가져온고 있다. 하지만 대부분의 웹 서비스에서 데이터의 요청과 응답은 비동기적으로 수행된다. 따라서 이번 튜토리얼에서 이것을 mocking하기 위해 getHeroes() 메소드는 Promise든 콜백함수든 Observable이든 어떤 종류든간에 비동기 방식으로 처리되어야 한다.

최종적으로는 Angular의 HttpClientget() 메소드를 이용하여 히어로 목록을 가져올 것인데 get() 메소드는 Observable을 반환하기 때문에 getHeroes() 메소드의 반환값을 Observable이 되도록 할 것이다.

ObservableRxJS 라이브러리의 핵심 클래스들 중의 하나이다. 다다음 챕터인 HTTP 에서 Angular의 HttpClient 메소드가 Observable을 리턴한다는 것을 알게 될 것이고 지금은 시뮬레이션을 위해 RxJSof() 메소드를 이용할 것이다.

of(HEORES)는 히어로 목록을 Observable로 만들어주고 이를 반환하게 된다.

HeroesComponent 구독하기(Subscribe)

HeroService에서 getHeroes() 메소드는 Observable을 반환하게 되었으므로 HeroesComponent에서 getHeroes() 메소드를 수정해야 한다.

수정 전에는 단순히 동기적으로 히어로 목록을 가져왔다면 이번에는 Observable이 히어로 목록을 방출(emit)할 때까지 기다린 후 방출이 되면 subscribe에서 콜백으로 그 값을 가져오게 된다.

메시지 보여주기

이번 섹션에는 다음과 같은 것을 해볼 것이다.

  • 화면 맨 아래 메시지를 표시하는 MessageComponent를 추가한다.
  • 앱 전체에 주입 가능한 MessageService를 생성한다.
  • HeroServiceMessageService를 주입한다.
  • HeroService가 히어로 목록을 성공적으로 가져올 때 메시지를 표시한다.

먼저 Angular CLI를 통해 MessageComponent를 생성한다.

ng generate component messages

이 컴포넌트를 전시하기 위해 app.component.html에 추가한다.

다음으로 MessageService를 생성한다.

ng generate service message

메시지를 담을 messages 배열을 정의하고 메시지 추가와 삭제 메소드를 구현한다.

이제 HeroService에 생성한 MessageService를 주입한다.

constructor(private messageService: MessageService) { }

그리고 히어로 목록을 가져올 때 메시지도 추가한다.

이제 메시지를 볼 수 있도록 MessageComponent를 수정한다.

템플릿 코드는 다음과 같이 수정한다.

요약

  • HeroService를 루트에 provider로 등록하여 앱 어디에서나 이 서비스를 주입할 수 있도록 하였다.
  • Angular 의존성 주입을 이용하여 HeroeService를 주입하였다.
  • HeroService에서 비동기적으로 반환하는 메소드를 구현하였다.
  • 히어로 목록을 Observable<Hero[]> 타입으로 반환할 수 있도록 하였다.
  • 클래스간의 느슨한 연결을 위해 메시지를 관리하는 MessageService를 만들었다.

7. Routing

SPA(Single Page Application)을 위해 한 페이지 내 에서 컴포넌트를 변경해가며 전시를 해야하므로 프론트엔드에서도 라우팅이 필요하다. 이를 클라이언트 사이드 내비게이션 구현 방식이라고 하며 Angular에서는 요청 URL 경로와 컴포넌트를 쌍으로 구성하여 처리한다.

appRoutingModule 생성

ng generate module app-routing --flat --module=app

RouterModuleimport하고 경로를 추가해준다.

그리고 이 라우터를 통해 전시되는 컴포넌트의 위치는 다음과 같이 router-outlet으로 구현한다.

다음은 사용자의 클릭을 라우터 탐색으로 바꾸는 디렉티브인 routerLink를 통해 라우터 경로대로 이동할 수 있도록 버튼을 생성한다.

대시보드 컴포넌트 생성

HeroesComponent와 마찬가지로 대시보드도 컴포넌트를 생성하고 라우터 경로를 추가한 후 routerLink를 통해 /dashboard 경로로 이동 시 라우터 검색을 할 수 있도록 한다.

먼저 DashboardComponent를 생성한다.

ng generate component dashboard

상위 3개를 가져오기 위해 slice를 수행한다.

ngFor 디렉티브를 통해 히어로 목록을 전시한다.

HeroDetailComponent 수정

HeroDetailComponent에서는 url에 명시된 히어로 아이디를 가져와 서비스에게 요청하는 방식으로 수정한다. 이 때 ActivatedRoutesnapshot에서 그 아이디를 가져온 후 히어로 서비스에 조회요청을 한다.

HeroService에 조회 기능을 추가한다.

요약

  • AppRoutingModule에서 라우터를 구성했다.
  • 간단한 경로, 리다이렉션 경로로 및 매개 변수화 된 경로(/:id)를 정의했다.
  • anchor 요소에서 routerLink 디렉티브를 사용하여 라우터 탐색을 하도록 했다.
  • 밀접하게 결합된 HeroDetailComponent를 분리했다.
  • 라우터 링크 매개 변수를 사용하여 사용자가 선택한 히어로의 상세 페이지로 이동했다.
  • 여러 구성 요소간에 HeroService를 공유했다.

8. HTTP

웹서비스는 서버에 데이터를 요청하고 그 응답 결과를 웹 페이지에 전시하는 형태가 될 것이다. 현재까지는 메모리 상의 데이터를 전시한 것이고 Angular의 HttpClientModule을 통해 HTTP 통신을 하는 방법을 알아볼 것이다.

웹 서버를 Mocking하기 위해 다음 모듈을 설치한다.

npm install angular-in-memory-web-api --save

Mock 데이터는 다음과 같이 정의한다.

HeroService에서 히어로를 조회하는 방식을 다음과 같이 바꾼다. HttpClient를 의존성 주입하고 get() 메소드를 이용해 서버로 요청한다. 이 때 tap() 연산자를 통해 로그를 찍고 catchError() 연산자를 통해 에러 발생 시 handleError() 핸들러 메소드를 호출하도록 한다. 그리고 이 두 연산자를 pipe()를 통해 체이닝한다.

  • tap(): 로깅하는 것과 같이 어떠한 사이드 이팩트가 없고 투명하게 수행한다.
  • catchError(): 옵저버블 시퀀스에서 에러 발생 시 핸들러를 실행한다.
  • 그 외 다양한 연산자가 존재

히어로 업데이트

HeroService에서 업데이트 기능을 추가한다. 이 때 HttpClientput() 메소드를 이용한다.

HeroDetailComponent에서 업데이트를 할 수 있도록 한다. Observable 객체는 subscribe()를 해야만 반드시 수행한다.

히어로 등록

HeroService에 히어로를 등록하는 기능을 추가한다. 이 때 HttpClientpost() 메소드를 이용한다.

HeroesComponent에서 등록을 할 수 있도록 서비스를 호출한다.

히어로 삭제

HeroService에서 히어로를 삭제하는 기능을 추가한다. 이 때 HttpClientdelete() 메소드를 이용한다.

히어로 목록에 x 버튼을 생성한 후 delete()를 호출한다.

히어로 검색

먼저HeroService에 검색 기능을 추가한다.

HeroSearchComponent를 생성 후 다음과 같이 템플릿을 수정한다. 컴포넌트에서는 rxjs 연산자를 적절히 파이핑하여 다음과 같이 구현한다.

  • debounceTime(300)은 새로운 문자열 이벤트의 흐름이 300ms 동안 일시 중지 될 때까지 기다린 후 최신 문자열을 전달한다. 300ms보다 자주 요청을하지 않는다.
  • distinctUntilChanged()는 필터 텍스트가 변경된 경우에만 요청을 보낸다.
  • switchMap()debouncedistinctUntilChanged를 통해 검색하는 각 검색어에 대해 검색 서비스를 호출한다. 이전 검색 관측 값을 취소하고 파기하며 관찰 가능한 최신 검색 서비스 만 반환한다.

요약

  • 필수 모듈인 HTTP 모듈을 설치하여 HTTP 통신을 수행하였다.
  • web API를 통해 히어로 목록을 가져올 수 있도록 HeroService을 수정했다.
  • HeroService에 get, post, put, delete HTTP 메소드를 통해 기능을 구현했다.
  • 히어로를 추가, 편집 및 삭제할 수 있도록 컴포넌트를 수정했다.
  • 옵저버블 객체를 구독하고 콜백 함수로 방출된 값을 처리하였다.

결론

이로써 Angular 공식 사이트에 있는 튜토리얼을 모두 진행해보았다. 컴포넌트를 생성하고 뷰를 전시했으며 서비스를 통해 컴포넌트의 관심사를 분리하여 유연한 구조로 리팩토링하였다. SPA를 위해 프론트엔드에서 라우팅을 수행하였으며 HTTP 통신을 하고 이것을 옵저버 패턴으로 처리하였다.

이번 코드랩을 통해 Angular를 통해 개발 함에 있어 어떤 구성요소들로 구성되어 있고 어떤 키워드들이 있는지 알 수 있을 것이다.

--

--

이상훈
상훈 Devlog

Frontend Developer 😁😁 #angular #javascript #typescript #scala #node