히어로들의 여행 : 6. 서버에서 데이터 받아오기

Sang heon lee·2022년 9월 1일
0

히어로들의 여행

목록 보기
6/6

HTTP 서비스 추가하기

  • HttpClientModule 를 추가합니다.
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeroesComponent } from './component/heroes/heroes.component';
import { HeroDetailComponent } from './component/hero-detail/hero-detail.component';
import { MessagesComponent } from './component/messages/messages.component';
import { DashboardComponent } from './component/dashboard/dashboard.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent,
    MessagesComponent,
    DashboardComponent,
  ],
  imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

데이터 서버 목킹하기

  • in-memory Web API 모듈로 데이터 서버와 통신하는 부분을 대체합니다.

  • HttpClient 로 보내는 요청이나 받는 요청이 in-memory Web API 로 처리되며, 데이터가 저장되고 반환하는 것도 이 모듈을 활용합니다.

  • in-memory Web API는 Angular가 제공하는 기능이 아닙니다.

  • npm install angular-in-memory-web-api 설치

  • HttpClientModule, HttpClientInMemoryWebApiModule, InMemoryDataService 모듈 등록 및 설정

// src/app/app.module.ts

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeroesComponent } from './component/heroes/heroes.component';
import { HeroDetailComponent } from './component/hero-detail/hero-detail.component';
import { MessagesComponent } from './component/messages/messages.component';
import { DashboardComponent } from './component/dashboard/dashboard.component';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './service/in-memory-data.service';

@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent,
    MessagesComponent,
    DashboardComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule,  // ⭐️⭐️⭐️⭐️⭐️⭐️
    HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
      dataEncapsulation: false   // ⭐️⭐️⭐️⭐️⭐️⭐️
    }),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
  • ng generate service InMemoryData 수행하여 Service 생성
// src/app/service/in-memory-data.service.ts

import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hero } from '../model/data';

@Injectable({
  providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
  constructor() {}

  createDb() {
    const heroes = [
      { id: 11, name: 'Dr 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' },
    ];
    return { heroes };
  }
	
  // 히어로 객체가 항상 id 프로퍼티를 갖도록 getId 메소드를 오버라이드 합니다.
  // 오버라이드?????
  // 히어로 목록이 비어있다면 이 메소드는 초기값(11)을 반환합니다.
  // 히어로 목록이 비어있지 않으면 히어로 id의 최대값에 1을 더해서 반환합니다.
  genId(heroes: Hero[]): number {
    return heroes.length > 0
      ? Math.max(...heroes.map((hero) => hero.id)) + 1
      : 11;
  }
}

히어로 데이터와 HTTP

  • http 변수에 의존성 주입.

  • HttpClient 메소드는 데이터를 하나만 반환합니다. 그래서 요청이 한번 있으면 응답도 하나 뿐입니다.

  • 일반적으로 옵저버블은 여러 번에 걸쳐 여러 데이터를 반환할 수 있습니다. 하지만 HttpClient가 반환하는 옵저버블은 데이터를 한번만 반환하고 종료되며, 다시 사용되지 않습니다.

    // src/app/service/hero.service.ts
    
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    ...
    
    constructor(
      private messageService: MessageService,
      private http: HttpClient
    ) {}

에러 처리하기

// src/app/service/hero.service.ts

  /**
   * HTTP 요청이 실패한 경우를 처리합니다.
   * 애플리케이션 로직 흐름은 그대로 유지됩니다.
   * @param operation
   * @param result
   * @returns
   */
  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      // TODO : 리모트 서버로 에러 메시지 보내기
      console.error(error);

      // TODO : 사용자가 이해할 수 있는 형태로 변환하기
      this.log(`${operation} failed: ${error.message}`);

      // 애플리케이션 로직이 끊기지 않도록 기본값으로 받은 객체를 반환합니다.
      return of(result as T);
    };
  }

히어로 목록 데이터 가져오기

  • rxjs연산자 : catchError => observable이 실패 했을 때 실행되는 연산자입니다.

  • rxjs연산자 : tap => observable data를 사용해서 어떤 동작을 수행하는데, 옵저버블 데이터는 변경하지 않고 그대로 전달합니다.

  // src/app/service/hero.service.ts

  /**
   * 히어로 데이터 목록 가져오기
   * @returns
   */
   getHeroes(): Observable<Hero[]> {
     return this.http.get<Hero[]>(this.heroesUrl).pipe(
       tap((_) => this.log('fetched heroes')),
       catchError(this.handleError<Hero[]>('getHeroes', []))
      );
   }

id로 히어로 데이터 가져오기

// src/app/service/hero.service.ts

/**
* 히어로 데이터 Id로 검색하여 가져오기
* server : GET: id에 해당하는 히어로 데이터 가져오기. 존재하지 않으면 404를 반환합니다.
* @param id
* @returns
*/

getHero(id: number): Observable<Hero> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.get<Hero>(url).pipe(
    tap((_) => this.log(`fetched hero id= ${id}`)),
    catchError(this.handleError<Hero>(`getHero id=${id}`))
  );
}

히어로 데이터 수정하기

// src/app/component/hero-detail/hero-detail.component.ts

save(): void {
  if (this.hero) {
    this.heroService.updateHero(this.hero).subscribe(() => this.goBack());
  }
}
// src/app/service/hero.service.ts

updateHero(hero: Hero): Observable<any> {
  return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
    tap((_) => this.log(`updated hero id=${hero.id}`)),
    catchError(this.handleError<any>('updateHero'))
  );
}

httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
};

새 히어로 추가하기

  • 변경, 삭제에 대해서 서버상에서의 변경, 삭제는 Service에서 요청 하지만 로컬에서의 변경은 컴포넌트가 담당해야 한다.

  • 히어로 이름 입력을 받을수 있게 HTML 에 문구 추가

// src/app/component/heroes/heroes.component.html

<h2>My Heroes</h2>

<div>
  <label for="new-hero">Hero name:</label>
  <input id="new-hero" #heroName />

  <button class="add-button" (click)="add(heroName.value); heroName.value = ''">
    Add hero
  </button>
</div>

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <a routerLink="/detail/{{ hero.id }}">
      <span class="badge">{{ hero.id }}</span> {{ hero.name }}
    </a>
    <button class="delete" title="delete hero" (click)="delete(hero)">X</button>
  </li>
</ul>
  • Add hero 버튼 클릭시 add() 함수가 실행되고 input값을 ''로 초기화한다.
// src/app/component/heroes/heroes.component.ts

add(name: string): void {
    name = name.trim();
    if (!name) {
      return;
    }
    this.heroService
      .addHero({ name } as HSModel.Hero)
      .subscribe((hero) => this.heroes.push(hero));
  }
  • addHero 서비스를 정의한다. post 메소드를 쓴다.
// src/app/service/hero.service.ts

/**
 * 히어로 데이터 추가하기
 * @param hero
 * @returns
 */
addHero(hero: HSModel.Hero): Observable<HSModel.Hero> {
  return this.http
    .post<HSModel.Hero>(this.heroesUrl, hero, this.httpOptions)
    .pipe(
      tap((newHero: HSModel.Hero) =>
        this.log(`added hero w/ id=${newHero.id}`)
      ),
      catchError(this.handleError<HSModel.Hero>(`addHero`))
    );
}

히어로 삭제하기

// src/app/component/heroes/heroes.component.html

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <a routerLink="/detail/{{ hero.id }}">
      <span class="badge">{{ hero.id }}</span> {{ hero.name }}
    </a>
    <button class="delete" title="delete hero" (click)="delete(hero)">X</button>
  </li>
</ul>
// src/app/component/heroes/heroes.component.ts

delete(hero: HSModel.Hero): void {
    this.heroes = this.heroes.filter((h) => h !== hero);
    this.heroService.deleteHero(hero.id).subscribe();
  }
// src/app/service/in-memory-data.service.ts

  /**
   * 히어로 데이터 삭제하기
   * @param id
   * @returns
   */
  deleteHero(id: number): Observable<HSModel.Hero> {
    const url = `${this.heroesUrl}/${id}`;

    return this.http.delete<HSModel.Hero>(url, this.httpOptions).pipe(
      tap((_) => this.log(`deleted hero id=${id}`)),
      catchError(this.handleError<Hero>('deleteHero'))
    );
  }
}

이름으로 검색하기

profile
개초보

0개의 댓글