Services(@Injectable, OnInit, ngOnInit)

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 Tour of Heroes / Multiple Components(@input)에서 이어집니다.

tour-of-heroes.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard 0c706ee
git reset --soft 0977d7b
npm install
atom .

tour-of-heroes.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 tour-of-heroes.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/tour-of-heroes.git
cd tour-of-heroes
git reset --hard 0c706ee
git reset --soft 0977d7b
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/tour-of-heroes/tree/0c706ee0bc8f405d95cb53c766e1240bdf7ddc31


Angular사이트의 공식 tutorial인 Tour of Heroes의 네번째 강의, Services입니다.

공식 tutorial link : https://angular.io/docs/ts/latest/tutorial/toh-pt4.html
온라인 예제 : https://angular.io/generated/live-examples/toh-pt4/eplnkr.html

이번에도 완성된 사이트의 기능상에는 변경이 없지만, heroes list를 HEROES에서 바로 가져오는 것이 아니라 service를 통해서 가져오게 됩니다.

Service

Service는 여러 component에서 필요로 하는 기능을 묶은 class입니다. 즉 Angular 사이트에서 사용되는 특정한 기능들을 service로 생성한 후 해당 기능이 요구되는 component는 이 service를 불러와서 사용할 수 있습니다. 코드가 중복되는 것을 막고, 코드의 관리의 편의성을 위해, 프로그램의 효율성 향상를 위해 사용됩니다.

우리가 이 포스트에서 service로 만들 기능은 "heroes list를 받아올 것"이며, HeroService class에 getHeroes 함수로 등록됩니다. 

이 heroes list는 app.component가 실행될 때 받아와야 하는데, 이처럼 component가 실행되는 순간 실행되는 코드들은 ngOnInit 함수에 저장됩니다. ngOnInit의 사용법도 알아보겠습니다.

폴더구조


heroes 데이터를 담고 있는 mock-heroes.ts, hero service를 가지고 있는 hero.service.ts가 생성되었고 app.component가 변경되었습니다.

코드

// src/app/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'}
];

원래 app.compoent.ts에 있던 HEROES가 파일로 분리되었습니다. 이 파일은 service에서 호출됩니다.

// src/app/hero.service.ts

import { Injectable } from '@angular/core'; //1

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable() //1
export class HeroService {
  getHeroes(): Promise<Hero[]> { //2
    return Promise.resolve(HEROES);
  }

  // See the "Take it slow" appendix
  getHeroesSlowly(): Promise<Hero[]> { //3
    return new Promise(resolve => {
      // Simulate server latency with 2 second delay
      setTimeout(() => resolve(this.getHeroes()), 2000);
    });
  }
}

1. @Injectable()은 이 class를 service로 만들어 줍니다. 즉 service class에 반드시 붙여야 하는 decorator입니다.

2. Promise를 사용해서 mock-heroes.ts에 선언된 HEROES를 가져옵니다. 다른 파일에 있는 상수를 가져오기 위해서는 promise를 사용할 필요가 없지만 나중에 다른 API로 부터 가져오는 것으로 고칠 부분이기 때문에 미리 promise를 사용하고 있습니다.

3. 비동기(asynchronous)로 데이터 받는 것을 시뮬레이션하기 위해 promise에 setTimeout을 사용하는 함수가 따로 생성되었습니다. 현재 코드에서는 사용되지 않고 있지만, 아래 app.component.ts에서 heroService.getHeroes() 대신heroService.getHeroesSlowly()를 사용하면 hero list가 2초 후에 표시되는 것을 테스트할 수 있습니다. 화살표 함수(=>)가 사용되었는데, 이 후에도 계속 사용되므로 화살표 함수 사용법이 익숙하지 않으면 반드시 익히고 진행하시기 바랍니다.

// src/app/app.component.ts

import { Component, OnInit } from '@angular/core'; //2-1

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

@Component({
  selector: 'my-app',
  template: `
    //...
  `,
  styles: [`
    //...
  `],
  providers: [HeroService] //1-1
})
export class AppComponent implements OnInit { //2-2
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private heroService: HeroService) { } //1-2

  getHeroes(): void { //1-3
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void { //2-3
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

// ... 로 표시된 부부은 .전과 비교해서 변화가 없는 부분입니다.

1-1. service들은 provider로 등록되어야 사용가능합니다.  @component의 providers에 등록합니다.

1-2. 생성자(constructor)를 통해서 HeroService를 해당 component의 heroService로 불러오는 과정입니다. 이 부분이 이해가 안되시면 일단은 service는 이렇게 component에 불러진다는 것만 알고 넘어가셔도 좋습니다.

1-3. 이제 this.heroService를 통해서 HeroService에 있는 함수들을 사용할 수 있습니다. HeroService의 getHeroes함수를 호출하고 그 결과를 this.heroes에 담는 getHeroes함수입니다. HeroService의 getHeroes함수가 promise를 리턴하기 때문에 then을 사용하여 결과값을 받습니다. 만약 비동기 호출 테스트를 하고 싶으시면 this.heroService.getHeroes() 대신에 this.heroService.getHeroesSlowly()를 사용하시면 됩니다.

2-1,2,3. Component가 준비(initiate)되는 즉시 실행되는 함수는ngOnInit인데, 이는 @angular/core 패키지의 OnInit 인터페이스에 있습니다. 이를 사용하기 위해서는 OnInit 를 import(2-1)하고, OnInit을 Component에 상속(implement)시키고(2-2), ngOnInit에 할일을 작성(2-3)하면 됩니다.
AppComponent가 준비되면 1번에서 만든 getHeroes가 호출됩니다.

마치며..

이번 포스트에서 hero.service의 기능은 list를 받아오는 기능 하나뿐이지만 나중에 모든 CRUD 기능을 HeroService에 담게 됩니다. 실제 angular 사이트 제작시에도 나중에 확장이 될 가능성이 높은 기능들(CRUD기능들은 항상)은 현재 는 기능이 하나더라도 처음부터 service로 만듭니다.

이번 강의에서 처음으로 화살표 함수와 promise가 나왔는데, 앞으로도 계속 사용되므로 익숙하지 않으신 분들은 반드시 익히고 다음으로 넘어가 주세요.

다음 강의는 route만들기입니다!

댓글

하영아빠 2017.07.13
이번 페이지는 어려웠지만 몇번 보니 이해가 되었습니다. 감사합니다.
I
Ian H 2017.07.13
@하영아빠,
이해가 잘 안되시면 일단은 사용법만 익히시고 넘어가셔도 됩니다 화이팅!
파빌리온 2017.08.03
hero.service.ts에 있는 getHeroes() 함수는 리턴할 때 json 형식으로 리턴하나요? 
I
Ian H 2017.08.04
@파빌리온,
아니요, getHeroes함수는 Promise를 return합니다. 
getHeroes(): Promise<Hero[]> { //2     return Promise.resolve(HEROES);   } 여기 보시면 함수 이름뒤 :와 블록 시작 { 사이에 Promise<Hero[]>라고 되어 있잖아요? 이게 return 타입을 나타냅니다. 
Promise에서 HEROES를 resolve로 보내구요.   HEROES는 mock-heroes.ts에서 오는데, JSON은 아니고 그냥 Javascript 오브젝트입니다^^
S
SunWoong Lee 2018.08.13
Promise<Hero[]> 라는 뜻이 Promise 객체를 리턴한다는걸 명시한다는 것은 알겠는데 <Hero[]> 부분이 이해가 가지 않네요 ㅠㅠ..  왜 그냥 Promise 도 아니고 Promise<Hero[]> 가 되는건가요?..
라고  질문을 쓰고 검색을 해서  좀 찾아보았는데요
결국 Promise가 resolve를 통해 이후로 전달해줄 값에 대한 정의였네요. Hero 객체 배열.
Promise는 결국 Promise 객체 안의 함수가 실행된 후 resolve 시킬것인지 reject 시킬 것인지 정의해주어서
다른 곳에서 그 객체 안의 함수를 실행해보고 /그 후의 상태를 정하는것 + 에러일 때의 처리/ 를 할 수 있게 해주는 객체네욤
I
Ian H 2018.08.13
@SunWoong Lee,
맞습니다^^ 기존의 Promise의 글의 설명이 좀 부족한 것 같아서 https://www.a-mean-blog.com/ko/blog/토막글/_/Javascript-Promise의 글을 수정하였습니다. 
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP