本教學示範如何把 HTTP 和 API 整合到你的應用程式中。
到目前為止,你的應用程式都是從 Angular 服務中的靜態陣列讀取資料。下一步是改用 JSON 伺服器,讓應用程式透過 HTTP 與它通訊。這個 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) { console.log( `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${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) { console.log( `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${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) { console.log( `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${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) { console.log( `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${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> `, styleUrl: './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> `, styleUrl: './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 建置更複雜的應用程式了。