이번 실습에서는 앱에 HTTP 통신과 API를 연결하는 방법을 알아봅니다.
지금까지 앱은 Angular 서비스의 정적 배열에서 데이터를 읽었습니다. 다음으로 HTTP를 통해 앱과 통신하는 JSON 서버를 사용합니다. HTTP 요청으로 실제 서버에서 데이터를 가져오는 과정을 실습합니다.
IMPORTANT: 이 실습은 로컬 환경에서 진행하는 것을 권장합니다.
학습 목표
앱에서 JSON 서버의 데이터를 사용합니다.
-
JSON 서버 설정하기
JSON Server는 모의 REST API를 만드는 오픈 소스 도구입니다. 현재 주택 서비스에 저장된 데이터를 이 도구로 제공합니다.
다음 명령어로 npm의
json-server를 설치하세요.npm install -g json-server프로젝트 루트 디렉터리에
db.json파일을 만드세요. 이 파일에json-server가 제공할 데이터를 저장합니다.db.json을 열고 다음 코드를 복사하세요.{ "locations": [ { "id": 0, "name": "Acme Fresh Start Housing", "city": "Chicago", "state": "IL", "photo": "https://angular.dev/assets/images/tutorials/common/bernard-hermant-CLKGGwIBTaY-unsplash.jpg", "availableUnits": 4, "wifi": true, "laundry": true }, { "id": 1, "name": "A113 Transitional Housing", "city": "Santa Monica", "state": "CA", "photo": "https://angular.dev/assets/images/tutorials/common/brandon-griggs-wR11KBaB86U-unsplash.jpg", "availableUnits": 0, "wifi": false, "laundry": true }, { "id": 2, "name": "Warm Beds Housing Support", "city": "Juneau", "state": "AK", "photo": "https://angular.dev/assets/images/tutorials/common/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg", "availableUnits": 1, "wifi": false, "laundry": false }, { "id": 3, "name": "Homesteady Housing", "city": "Chicago", "state": "IL", "photo": "https://angular.dev/assets/images/tutorials/common/ian-macdonald-W8z6aiwfi1E-unsplash.jpg", "availableUnits": 1, "wifi": true, "laundry": false }, { "id": 4, "name": "Happy Homes Group", "city": "Gary", "state": "IN", "photo": "https://angular.dev/assets/images/tutorials/common/krzysztof-hepner-978RAXoXnH4-unsplash.jpg", "availableUnits": 1, "wifi": true, "laundry": false }, { "id": 5, "name": "Hopeful Apartment Group", "city": "Oakland", "state": "CA", "photo": "https://angular.dev/assets/images/tutorials/common/r-architecture-JvQ0Q5IkeMM-unsplash.jpg", "availableUnits": 2, "wifi": true, "laundry": true }, { "id": 6, "name": "Seriously Safe Towns", "city": "Oakland", "state": "CA", "photo": "https://angular.dev/assets/images/tutorials/common/phil-hearing-IYfp2Ixe9nM-unsplash.jpg", "availableUnits": 5, "wifi": true, "laundry": true }, { "id": 7, "name": "Hopeful Housing Solutions", "city": "Oakland", "state": "CA", "photo": "https://angular.dev/assets/images/tutorials/common/r-architecture-GGupkreKwxA-unsplash.jpg", "availableUnits": 2, "wifi": true, "laundry": true }, { "id": 8, "name": "Seriously Safe Towns", "city": "Oakland", "state": "CA", "photo": "https://angular.dev/assets/images/tutorials/common/saru-robert-9rP3mxf8qWI-unsplash.jpg", "availableUnits": 10, "wifi": false, "laundry": false }, { "id": 9, "name": "Capital Safe Towns", "city": "Portland", "state": "OR", "photo": "https://angular.dev/assets/images/tutorials/common/webaliser-_TPTXZd9mOo-unsplash.jpg", "availableUnits": 6, "wifi": true, "laundry": true } ] }파일을 저장하세요.
설정을 테스트해 보겠습니다. 프로젝트 루트 디렉터리의 명령줄에서 다음 명령어를 실행하세요.
json-server --watch db.json웹 브라우저에서
http://localhost:3000/locations를 열고 응답에db.json의 데이터가 포함되어 있는지 확인하세요.
설정에 문제가 있다면 공식 문서에서 자세한 내용을 확인할 수 있습니다.
-
로컬 배열 대신 웹 서버를 사용하도록 서비스 수정하기
데이터 소스가 준비되었으므로 웹 앱을 연결해 해당 데이터를 사용하도록 수정합니다.
src/app/housing.service.ts를 다음과 같이 수정하세요:housingLocationList프로퍼티와 데이터 배열,baseUrl프로퍼티를 삭제하세요.url문자열 프로퍼티를 추가하고'http://localhost:3000/locations'를 할당하세요.housing.service.ts에 url 프로퍼티 추가하기
import {Service} from '@angular/core'; import {HousingLocationInfo} from './housinglocation'; @Service() export class HousingService { url = 'http://localhost:3000/locations'; async getAllHousingLocations(): Promise<HousingLocationInfo[]> { const data = await fetch(this.url); return (await data.json()) ?? []; } async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> { const data = await fetch(`${this.url}/${id}`); const locationJson = await data.json(); return locationJson ?? {}; } submitApplication(firstName: string, lastName: string, email: string) { // tslint:disable-next-line console.log(firstName, lastName, email); } }나머지 코드는
housingLocationList프로퍼티에 의존하므로 이 시점에는 오류가 발생합니다. 다음으로 서비스 메서드를 수정하겠습니다.getAllHousingLocations함수가 설정한 웹 서버를 호출하도록 수정하세요.housing.service.ts의 getAllHousingLocations 메서드 수정하기
import {Service} from '@angular/core'; import {HousingLocationInfo} from './housinglocation'; @Service() export class HousingService { url = 'http://localhost:3000/locations'; async getAllHousingLocations(): Promise<HousingLocationInfo[]> { const data = await fetch(this.url); return (await data.json()) ?? []; } async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> { const data = await fetch(`${this.url}/${id}`); const locationJson = await data.json(); return locationJson ?? {}; } submitApplication(firstName: string, lastName: string, email: string) { // tslint:disable-next-line console.log(firstName, lastName, email); } }이제 비동기 코드로 HTTP GET 요청을 보냅니다.
HELPFUL: 이 예제는
fetch를 사용합니다. 더 복잡한 기능이 필요하다면 Angular에서 제공하는HttpClient를 고려하세요.getHousingLocationById함수가 설정한 웹 서버를 호출하도록 수정하세요.HELPFUL:
fetch의 URL 뒤에id를 경로 구간으로 추가한${this.url}/${id}형식을 확인하세요. 이렇게 하면id프로퍼티가 일치하는 주택 정보를 요청할 수 있습니다.housing.service.ts의 getHousingLocationById 메서드 수정하기
import {Service} from '@angular/core'; import {HousingLocationInfo} from './housinglocation'; @Service() export class HousingService { url = 'http://localhost:3000/locations'; async getAllHousingLocations(): Promise<HousingLocationInfo[]> { const data = await fetch(this.url); return (await data.json()) ?? []; } async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> { const data = await fetch(`${this.url}/${id}`); const locationJson = await data.json(); return locationJson ?? {}; } submitApplication(firstName: string, lastName: string, email: string) { // tslint:disable-next-line console.log(firstName, lastName, email); } }모든 수정을 마친 서비스는 다음 코드와 같아야 합니다.
housing.service.ts의 최종 코드
import {Service} from '@angular/core'; import {HousingLocationInfo} from './housinglocation'; @Service() export class HousingService { url = 'http://localhost:3000/locations'; async getAllHousingLocations(): Promise<HousingLocationInfo[]> { const data = await fetch(this.url); return (await data.json()) ?? []; } async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> { const data = await fetch(`${this.url}/${id}`); const locationJson = await data.json(); return locationJson ?? {}; } submitApplication(firstName: string, lastName: string, email: string) { // tslint:disable-next-line console.log(firstName, lastName, email); } } -
서비스의 비동기 호출을 사용하도록 컴포넌트 수정하기
이제 서비스는 HTTP 요청으로 데이터를 읽지만, 이를 사용하는 컴포넌트는 동기 방식의 서비스를 기준으로 작성되어 오류가 발생합니다.
src/app/home/home.ts의constructor에서 비동기로 바뀐getAllHousingLocations메서드를 사용하도록 수정하세요. 상태에 시그널을 사용하지 않았으므로 화면과 동기화할 변경이 발생했음을 Angular에 알려야 합니다. 이를 위해this.changeDetectorRef.markForCheck()를 호출하세요.
home.ts의 constructor 수정하기
import {ChangeDetectorRef, Component, inject} from '@angular/core'; import {HousingLocation} from '../housing-location/housing-location'; import {HousingLocationInfo} from '../housinglocation'; import {HousingService} from '../housing.service'; @Component({ selector: 'app-home', imports: [HousingLocation], template: ` <section> <form> <input type="text" placeholder="Filter by city" #filter /> <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button> </form> </section> <section class="results"> @for (housingLocation of filteredLocationList; track $index) { <app-housing-location [housingLocation]="housingLocation" /> } </section> `, styleUrls: ['./home.css'], }) export class Home { private readonly changeDetectorRef = inject(ChangeDetectorRef); housingLocationList: HousingLocationInfo[] = []; housingService: HousingService = inject(HousingService); filteredLocationList: HousingLocationInfo[] = []; constructor() { this.housingService .getAllHousingLocations() .then((housingLocationList: HousingLocationInfo[]) => { this.housingLocationList = housingLocationList; this.filteredLocationList = housingLocationList; this.changeDetectorRef.markForCheck(); }); } filterResults(text: string) { if (!text) { this.filteredLocationList = this.housingLocationList; return; } this.filteredLocationList = this.housingLocationList.filter((housingLocation) => housingLocation?.city.toLowerCase().includes(text.toLowerCase()), ); } }src/app/details/details.ts의constructor에서 비동기로 바뀐getHousingLocationById메서드를 사용하도록 수정하세요. 앞에서와 마찬가지로this.changeDetectorRef.markForCheck()를 호출해 변경 사항을 Angular에 알려야 합니다.
details.ts의 constructor 수정하기
import {ChangeDetectorRef, Component, inject} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {HousingService} from '../housing.service'; import {HousingLocationInfo} from '../housinglocation'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; @Component({ selector: 'app-details', imports: [ReactiveFormsModule], template: ` <article> <img class="listing-photo" [src]="housingLocation?.photo" alt="Exterior photo of {{ housingLocation?.name }}" crossorigin /> <section class="listing-description"> <h2 class="listing-heading">{{ housingLocation?.name }}</h2> <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p> </section> <section class="listing-features"> <h2 class="section-heading">About this housing location</h2> <ul> <li>Units available: {{ housingLocation?.availableUnits }}</li> <li>Does this location have wifi: {{ housingLocation?.wifi }}</li> <li>Does this location have laundry: {{ housingLocation?.laundry }}</li> </ul> </section> <section class="listing-apply"> <h2 class="section-heading">Apply now to live here</h2> <form [formGroup]="applyForm" (submit)="submitApplication()"> <label for="first-name">First Name</label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name</label> <input id="last-name" type="text" formControlName="lastName" /> <label for="email">Email</label> <input id="email" type="email" formControlName="email" /> <button type="submit" class="primary">Apply now</button> </form> </section> </article> `, styleUrls: ['./details.css'], }) export class Details { private readonly changeDetectorRef = inject(ChangeDetectorRef); route: ActivatedRoute = inject(ActivatedRoute); housingService = inject(HousingService); housingLocation: HousingLocationInfo | undefined; applyForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), email: new FormControl(''), }); constructor() { const housingLocationId = parseInt(this.route.snapshot.params['id'], 10); this.housingService.getHousingLocationById(housingLocationId).then((housingLocation) => { this.housingLocation = housingLocation; this.changeDetectorRef.markForCheck(); }); } submitApplication() { this.housingService.submitApplication( this.applyForm.value.firstName ?? '', this.applyForm.value.lastName ?? '', this.applyForm.value.email ?? '', ); } }코드를 저장하세요.
브라우저에서 애플리케이션을 열고 오류 없이 실행되는지 확인하세요.
NOTE: 이 실습에서는 브라우저의 fetch API를 사용합니다. 인터셉터가 필요하다면 HTTP 클라이언트 문서를 참고하세요.
SUMMARY: 이번 실습에서는 앱이 로컬 웹 서버(json-server)를 사용하고 비동기 서비스 메서드로 데이터를 가져오도록 수정했습니다.
축하합니다! 튜토리얼을 모두 마쳤으며 더 복잡한 Angular 앱을 개발할 준비가 되었습니다.