ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2-4 서비스 분리
    앵귤러/01 퀵스타트 & 튜토리얼 2017. 8. 6. 10:09

    서비스 분리(Services)

    컴포넌트 마다 데이터를 액세스 하는 동일한 코드가 있을 수 있다. 이러한 코드들을 모아서 서비스로 만들어 재사용할 수 있다. 컴포넌트에서 데이터액세스 부분을 제거함으로써 좀더 뷰에 집중할 수 있으며, 모조 서비스(mock service)를 이용하여 유닛테스트를 좀더 쉽게 할 수 있다.

     

    이전 장에서 만든 어플리케이션을 리팩토링 해보자.

    app.component.ts파일을 hero.service.ts, mock-heroes.ts, app.component.ts파일로 분리하였다.

    ng명령어 : ng g service hero

    src/app/hero.service.ts

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

    import { Hero }  from './hero';

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

     

    @Injectable()

    export class HeroService {

     

      getHeroes() : Promise<Hero[]>{

        return Promise.resolve(HEROES);

      }

    }

     

    @Injectable()

    서비스 클래스에는 @Injectable 데코레이터를 달아준다.

    1. 스캇 앨런(K. Scott Allen)이 작성한 AngularJS 추상화 : 서비스란?

     1) 어플리케이션 공통 로직으로서의 서비스

     -컨트롤러, 지시자, 다른 서비스는 모두 특정 서비스의 하나 이상의 의존관계를 가질 수 있거나 갖지 않을 수 있다. , 서비스는 어플리케이션의 각 다른 부분에서 공통으로 사용하는 코드를 담기에 좋은 곳이라는 말이다. 가령 여러 컨트롤러에서 특정 계산 알고리즘을 필요로 한다면 해당 알고리즘을 구현한 코드가 있는 서비스를 만들어 해당 알고리즘을 필요로 하는 컨트롤러가 이 서비스를 사용하면 되는 것이다.

     

     2) 싱글톤(Singleton)으로서의 서비스

     - Angular는 서비스를 싱글톤으로 관리한다. , 어플리케이션에서 서비스의 인스턴스를 오직 하나만 가지고 있게 한다는 것이다. 이는 서비스가 어플리케이션이 살아있는 동안에 유지해야 할 데이터를 보관하는 장소로 적당하다는 의미다. 가령 화면이 변경되어 여러 컨트롤러와 모델들이 호출되고 다시 사라지는 동안 유지해야할 데이터가 있으면 서비스를 이용해 유지할 수 있다는 의미다.

     

     3) 커뮤니케이션 허브(hub)로서의 서비스

     - Angular는 관심사의 분리(seperation of concerns)를 할 수 있고 다양한 컴포넌트 사이에 서로의 존재 여부를 모르게 하기 위한 프레임워크 기능을 제공한다. 서비스는 컨트롤러, 지시자, 필터, 그리고 다른 서비스에 주입되므로 다른 컴포넌트 간에 느슨하게 연결(loosely coupled)하며 서로 커뮤니케이션을 하게 하는 역할을 하게 된다.

     

     4) 의존성 주입 대상(injectable Dependencies)으로서의 서비스

     - 서비스를 가장 많이 사용하는 이유 중 하나는 서비스가 컨트롤러, 지시자, 필터, 그리고 다른 서비스(다른 컴포넌트)에 주입되기 때문이다. 이는 각 컴포넌트 사이에 느슨하게 연결을 하게 해주며 단위 테스트시에 얼마든 주입되는 컴포넌트를 대체시킬 수 있게 한다. Angular http, log, window 등 다양한 서비스를 제공하고 이러한 서비스를 다른 컴포턴트에게 주입할 수 있는 대상이 된다.

    - 이처럼 서비스는 각 컨트롤러 사이의 데이터를 공유하게 해주며 어플리케이션에서 다루는 객체의 싱글톤을 유지하게 해준다. 이러한 서비스는 컨트롤러, 다른 서비스, 또는 지시자에서 주입되는 대상이 되는 것이다.

     

    Promise

    getHeroes메서드에서 HEROES 모조 배열객체를 바로 호출할 수 있지만 일반적으로 http호출을 통해 원격 서버에서 json호출을 처리한다. 이럴 경우에 서버로부터 응답을 기다려야 하고, 기다리는 동안 브라우저는 응답을 기다리지 않은 체로 다른 UI처리를 진행한다.

    Promise는 서버로부터 결과가 도착했을 때 다시 우리를 호출하라는 약속이다.

    Promise에 대해 좀더 자세히 알고싶으면 http://www.datchley.name/es6-promises/를 참조.

    getHeroes() {

      return Promise.resolve(HEROES);

    }

    서비스에서 리턴 값을 HOROES에서 Promise.resolve함수를 처리하였기 때문에 컴포턴트에서도 결과처리를 다르게 하여야 한다.

     

    src/app/mock-heroes.ts

    import {Hero} from './hero';

     

    export const HEROES: Hero[] =[

        { "id": 11, "name": "홍길동"},

        { "id": 12, "name": "이순신"},

        { "id": 13, "name": "수퍼맨"},

        { "id": 14, "name": "배트맨"},

        { "id": 15, "name": "로빈훗"},

        { "id": 16, "name": "장보고"},

        { "id": 17, "name": "을지문덕"},

        { "id": 18, "name": "김유신"},

        { "id": 19, "name": "간디"},

        { "id": 20, "name": "강감찬"}

    ];

     

    모조 데이터 파일을 별도로 관리하기 위해 분리한다. HEROES변수는 HeroService에서 사용하기 위해 export한다.

     

    src/app/app.component.ts

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

    import {Hero}   from './hero';

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

     

    @Component({

        selector: 'app-root',

        template:`

        <h1>{{title}}</h1>

        <h2>나의 영웅들</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>

        <hero-detail [hero]="selectedHero"></hero-detail>

        `,

        styles:[`

        .selected{

            background-color: #CFD8DC !important;

        }

        .heroes{

            margin: 0 0 2em 0;

            list-style-type: none;

            padding: 0;

            width: 10em;

        }

        .heroes li {

            cursor: pointer;

            position: relative;

            left: 0;

            background-color: #EEE;

            margin: .5em;

            padding: .3em 0em;

            height: 1.6em;

            border-radius: 4px;

        }

        .heroes li:hover{

            color: #607D8B;

            background-color: #EEE;

            left: .1em;

        }

        .heroes .text{

            position: relative;

            top: -3px;

        }

        .heroes .badge{

            display: inline-block;

            font-size: small;

            color: white;

            padding: 0.8em 0.7em 0em 0.7em;

            background-color: #607D8B;

            line-height:1em;

            position: relative;

            left: -1px;

            top: -4px;

            margin-right: .8em;

            border-radius: 4px 0px 0px 4px;

        }

        `],

        providers : [HeroService]

    })

    export class AppComponent implements OnInit {

        constructor(private heroService: HeroService){}

     

        ngOnInit(): void {

            this.getHeroes();

        }

     

        public title = '영웅여행';

        heroes: Hero[];

        selectedHero : Hero;

        onSelect(hero:Hero) {

            this.selectedHero = hero;

        }

        getHeroes(): void {

            this.heroService.getHeroes().then(heroes => this.heroes = heroes);

        }

    }

     

    Hero Service를 사용하기 위해서 새로운 인스턴스를 생성해야 한다.

    heroService = new HeroService(); // don't do this

    위와 같이 사용하는 것은 다음의 몇 가지 이유로 좋은 방법이 아니다.

    l  컴포넌트가 서비스를 create하는 방법을 알아야 한다. HeroService 생성자를 변경한다면, 서비스를 생성한 모든 곳을 찾아 수정하여야 한다. 이것은 오류를 만들 가능성이 높아지며, 테스트를 어렵게 한다.

    l  서비스를 사용하기 위해 new 생성자를 사용한 뒤, 혹여 heroes객체를 cache하고, 타 컴포넌트와 공유하고자 한다면 불가능하게 된다.

    l  다른 모조 서비스를 사용하기도 쉽지 않다.

     

    HeroService 주입방법

    1.      constructor 생성

    2.      @Component providers 메타데이타 추가

     

    constructor

    컴포넌트 생성자에서 HeroService private로 선언한다.

    constructor(private _heroService: HeroService) { }

    이렇게 함으로써 AngularAppComponent를 생성할 때 HeroService의 인스턴스를 제공하도록 한다.

    Angular Agular Dependency Injector를 통해서 인스턴스를 가져온다.

    Injector는 이전에 생성된 서비스들의 컨테이너로 서비스 호출이 발생할 때 이전에 생성된 서비스 인스턴스가 있을 경우 그것을 리턴하고, 없을 경우 인스턴스를 생성후 컨테이너에 추가후 리턴한다.

     

    @Component providers 메타데이타

    여기까지 코딩 후 실행시키면 다음과 같은 오류가 발생한다.

    EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)

    InjectorHeroService를 생성하는 방법을 모르기 때문에 발생한 오류이다.

    providerHeroService를 등록함으로써 Injector에게 HeroService를 가져오는 방법을 알려준다.

    providers: [HeroService]

    providers배열은 Angular가 새로운 AppComponent를 생성할 때 HeroSerivce의 인스턴스를 생성하도록 알려준다.

    이로써 AppComponent는 컴포넌트는 HeroService를 사용할 수 있고, 모든 자식 컴포턴트들이 HeroService를 사용할 수 있다.

     

    ngOnInit Lifecycle Hook

    AppComponentgeHeroes메서드를 객체 생성자인 constructor메서드에서 사용하지 않았다. 이는 객체 생성시 비즈니스로직을 처리하는 것은 많은 복잡한 문제들을 일으킬 수 있다고 한다.

    생성자는 파라메타를 속성에 대입하는 정도의 단순 초기화에만 사용해야 한다.

    AngularngOnInit메서드를 사용하여 비즈니스로직을 호출하도록 제공하고 있다. 이 메서드에서 비즈니스 로직을 처리하면 컴포넌트 로드 시에 비지스로직을 처리할 수 있다.

    이 외에도 Angular에서 제공하는 Lifecycle Hook메서드가 아래와 같이 있는데, 순서대로 호출된다.

    l  ngOnChanges – 바인드 변수가 변경될 때 호출

    l  ngOnInit – 최초 ngOnChanges 메서드 호출 후

    l  ngDoCheck – 개발자의 맞춤형 변경 탐지(developer's custom change detection)

    l  ngAfterContentInit – 컴포넌트 내용 초기화 후

    l  ngAfterContentChecked – 컴포넌트 내용의 모든 체크 후

    l  ngAfterViewInit – 컴포넌트의 뷰가 초기화된 후

    l  ngAfterViewChecked – 컴포넌트 뷰의 모든 체크 후

    l  ngOnDestroy – Directive가 소멸되기 바로 전

     

    ngOnInit을 사용하기 위해서는 다음과 같은 구조로 작성되어야 한다.

    import {OnInit} from '@angular/core';

     

    export class AppComponent implements OnInit { //인터페이스 상속

      ngOnInit() { //메서드 구현

      }

    }

     

    Promise.then

    이전에 만들어진 getHoroes메서드는 아래와 같다.

    getHeroes() {

        this.heroes = this._heroService.getHeroes();

      }

    이제 서비스에서 배열을 리턴하지 않고 Promise.resolve로 변경하였기 때문에 해결된 약속(promise)에 대해 취해야 할 행동(callback function)을 구현해야 한다. Promisethen메서드에 callback functions을 파라미터로 전달한다.

    getHeroes() {

      this._heroService.getHeroes().then(heroes => this.heroes = heroes);

    }

     

    => 화살표 함수(Arrow function)

    화살표 함수는 익명함수라고도 한다.

    https://github.com/wlzla000/JavaScript-documents/blob/master/ECMAScript%206%20Arrow%20functions(Korean).md

     

    화살표 함수(arrow functions)

    화살표 함수의 특징

    화살표 함수(arrow function) ECMAScript 6에서 표준화되었다.

    화살표 함수는 익명 함수이고, 자기만의 this, arguments, super, new.target을 할당하지 않는다.

    화살표 함수 표현식의 구조

    함수의 인자 => 함수의 내용처럼 쓴다.

    인자가 하나일 경우에는 괄호로 묶지 않아도 되고, 하나가 아닐 경우에는 (a, b)와 같이 괄호로 묶어야 한다.

    내용은 중괄호({, })로 묶거나 묶지 않을 수 있다. 중괄호로 묶지 않으면 일반적인 표현식처럼 작용하고 중괄호로 묶으면 기존의 함수처럼 작용한다. , 화살표 함수 표현식의 내용이 객체 표현식일 경우에는 괄호 연산자((, ))로 감싸야 한다.

    화살표 함수 표현식의

    array => array.length
    // 배열 array의 길이를 반환하는 화살표 함수
    (stringA, stringB) => stringA + stringB
    // 문자열 stringA와 문자열 stringB를 합친 문자열을 반환하는 화살표 함수
    () => {alert("인자 없음."); console.log("undefined를 반환함."); 10;}
    () => {console.log("인자 없음. 10을 반환함."); return 10;}
    (...input) => ({value: input})
    // value 속성 값이 인자들이 담긴 배열인 객체를 반환하는 화살표 함수
    // 인자란에 전개 연산자(‘...’)를 사용하였다. 이 경우에는 반드시 인자란을 소괄호로 감싸야 한다.

    화살표 함수의 활용

    numberArray.sort((a, b) => a - b);
    // Number 형 값들이 담긴 배열 numberArray를 오름차순으로 정렬한다.
    ((...input) => ({value: input}))(1, 2, 3, 4, 5);
    // {value: [1, 2, 3, 4, 5]};
    // 인자란에 전개 연산자(‘...’)를 사용하였다. 이 경우에는 반드시 인자란을 소괄호로 감싸야 한다.
    function Person()
    {
      this.age = 0;
     
      setInterval(() => {
          this.age++; // 화살표 함수는 자기만의 this를 할당하지 않기 때문에 이 ‘this’는 이 Person 객체이다.
        }, 1000);
    }
     
    var personObject = new Person();

     

     

    댓글

Designed by Tistory.