// 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 변수에 의존성 주입.
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', []))
);
}
// 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>
// 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));
}
// 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'))
);
}
}