[Angular] Angular-first-app
跟著 Angular 官方教程一 the First Angular app 寫的筆記
Ch1. Hello World
第一堂教了些基礎語法和認識檔案結構
ng serve 啟動 angular 專案
src/
src/ 下面有三個基本檔案
index.html is the app's top level HTML template.
style.css is the app's top level style sheet.
main.ts is where the app start running.
app/
src/app/ 裡面會放入 angular 應用程式的元件,像是 app.component.ts,一開始長得如下,
import { Component } from "@angular/core";
@Component({
selector: "app-root", // 選擇器
standalone: true, // Angular components marked as standalone do not need to be declared in an NgModule
imports: [], // standalone components 才有的屬性,用來匯入其他 standalone components
template: `<h1>Hello world!</h1>`, // HTML
styleUrls: ["./app.component.css"], // CSS
})
export class AppComponent {
title = "homes";
}
配置檔
.angularhas files required to build the Angular app..e2ehas files used to test the app..node_moduleshas the node.js packages that the app uses.angular.jsondescribes the Angular app to the app building tools.package.jsonis used by npm (the node package manager) to run the finished app.tsconfig.*are the files that describe the app's configuration to the TypeScript compiler.
Ch2. Home component
這一堂建立 HomeComponent,在 termianl 切換到 app/,輸入以下指令
ng generate component Home --standalone --inline-template --skip-tests
在 app.component.ts 匯入
import { HomeComponent } from './home/home.component'; // 一開始的引入
@Component({
selector: 'app-root',
standalone: true,
imports: [HomeComponent], // 記得引入 standalone 元件
template: `
<main>
<header class="brand-name">
<img class="brand-logo" src="/assets/logo.svg" alt="logo" aria-hidden="true">
</header>
<section class="content">
<app-home></app-home>
</section>
</main>
`,
styleUrls: ['./app.component.css'],
})
並修改一些 template 和 css,形成一個具有搜尋欄的首頁

Ch3. HousingLocation component
建立 HousingLactionComponent
ng generate component HousingLocation --standalone --inline-template --skip-tests
在 src/app/home/home.component.ts 匯入使用
import { HousingLocationComponent } from '../housing-location/housing-location.component'; // 匯入新元件
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, HousingLocationComponent], // 加入新元件
template: `
<section>
<form>
<input type="text" placeholder="Filter by city">
<button class="primary" type="button">Search</button>
</form>
</section>
<section class="results">
<app-housing-location></app-housing-location>
</section>
`,
styleUrls: ['./home.component.css'],
})
Ch4. Creating an interface
這一章教我們怎麼建立 interface 來去規範我們的資料,透過語法
ng generate interface housinglocation
會建立出空白的 app/housinglocation.ts,在檔案內寫入我們的資料定義
export interface HousingLocation {
id: number;
name: string;
city: string;
state: string;
photo: string;
availableUnits: number;
wifi: boolean;
laundry: boolean;
}
另一邊,在 app/home/home.component.ts,匯入 interface,並且在最下方的 export class HomeComponent {} 建立 housingLocation 的實例延伸這個介面。
export class HomeComponent {
housingLocation: HousingLocation = {
id: 9999,
name: "Test Home",
city: "Test city",
state: "ST",
photo: "assets/example-house.jpg",
availableUnits: 99,
wifi: true,
laundry: false,
};
}
Ch5. Add an input parameter to the component
這一章教到 @Input() 的用法
import { Component, Input } from '@angular/core'; // 匯入 Input
import { CommonModule } from '@angular/common';
import { HousingLocation } from '../housinglocation'; // 資料介面
// ...
export class HousingLocationComponent {
@Input() housingLocation!: HousingLocation; // !: 告訴 ts ,housingLocation 不是 null 或 undefined, 而是會透過 angular Input 設計的方式傳入
}
Ch6. Add a property binding to a component’s template
上一章在 housing-location.component.ts 先建立了 @Input() 進入的變數,在他的父層也要透過 property binding (屬性繫結) 的方式,才能將資料傳入
<app-housing-location [housingLocation]="housingLocation"></app-housing-location>
Ch7. Add an interpolation to a component’s template
接下來,在取得 housingLocation 的資料後,我們可以透過 {{ expression }} 去把資料解釋給 HTML 呈現在畫面上
@Component({
selector: 'app-housing-location',
standalone: true,
imports: [CommonModule],
template: `
<section class="listing">
<img class="listing-photo" [src]="housingLocation.photo" alt="Exterior photo of {{housingLocation.name}}">
<h2 class="listing-heading">{{ housingLocation.name }}</h2>
<p class="listing-location">{{ housingLocation.city}}, {{housingLocation.state }}</p>
</section>
`,
styleUrls: ['./housing-location.component.css'],
})
畫面就會呈現如下

Ch8. Use *ngFor to list objects in component
接下來透過 *ngFor 的語法來去呈現物件陣列的資料
export class HomeComponent {
housingLocationList: HousingLocation[] = [
{
// ...
}
]
修正 template
<app-housing-location
*ngFor="let housingLocation of housingLocationList"
[housingLocation]="housingLocation">
</app-housing-location>
接下來,就可以看到和 housingLocationList 的陣列長度一樣多的 app-housing-location 元件。
Ch9. Angular services
透過語法 ng generate service housing --skip-tests 建立 housing.service.ts
在 housing.service.ts ,將 housingLocationList 寫入,並寫入兩個 getter() 去取資料
getAllHousingLocations(): HousingLocation[] {
return this.housingLocationList;
}
getHousingLocationById(id: number): HousingLocation | undefined {
return this.housingLocationList.find(housingLocation => housingLocation.id === id);
}
而在另一邊,HomeComponent 要注入 HousingService
import { Component, inject } from "@angular/core"; // inject 注入服務
import { HousingService } from "../housing.service";
export class HomeComponent {
housingLocationList: HousingLocation[] = [];
housingService: HousingService = inject(HousingService);
constructor() {
this.housingLocationList = this.housingService.getAllHousingLocations();
} // The constructor is the first function that runs when this component is created.
}
Ch10. Add routes to the application
這一章教我們怎麼建立路徑,這邊先建立一個無關緊要的 DetailsComponent
ng generate component details --standalone --inline-template --skip-tests
接下來都是重頭戲,在 src/app 中建立 routes.ts,匯出 routeConfig,裡面有路徑、元件和標題。
import { Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
import { DetailsComponent } from "./details/details.component";
const routeConfig: Routes = [
{
path: "",
component: HomeComponent,
title: "Home page",
},
{
path: "details/:id",
component: DetailsComponent,
title: "Home details",
},
];
export default routeConfig;
在 src/main.ts 中引入 provideRouter 和 routeConfig,也要在 bootstrapApplication 的地方,加入 routing 的設置。
import { provideRouter } from '@angular/router';
import routeConfig from './app/routes';
bootstrapApplication(AppComponent,
{
providers: [
provideProtractorTestingSupport(),
provideRouter(routeConfig) // 加入 config
]
}
).catch(err => console.error(err));
接下來,到 src/app/app.component.ts,匯入 RoutingModule,要修改 imports 還有 template,將原本的 <app-home></app-home> 改為 <router-outlet></router-outlet> 以及加入 <a [routerLink]="['/']"> 回到首頁。
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [
HomeComponent,
RouterModule, // 要加入
],
template: `
<main>
<a [routerLink]="['/']">
<header class="brand-name">
<img class="brand-logo" src="/assets/logo.svg" alt="logo" aria-hidden="true">
</header>
</a>
<section class="content">
<router-outlet></router-outlet>
</section>
</main>
`,
styleUrls: ['./app.component.css'],
})
// ...
接下來依據 routeConfig ,我們可以去 / 和 details/id 的頁面。
Ch11. Integrate details page into application
首先,在主頁會呈現 <app-housing-location> 的內容,我們在他的 template 多增加一個 routerLink 前往特定 /details/:id 的頁面
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-housing-location',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<section class="listing">
<img class="listing-photo" [src]="housingLocation.photo" alt="Exterior photo of {{housingLocation.name}}">
<h2 class="listing-heading">{{ housingLocation.name }}</h2>
<p class="listing-location">{{ housingLocation.city}}, {{housingLocation.state }}</p>
<a [routerLink]="['/details', housingLocation.id]">Learn More</a>
</section>
`,
styleUrls: ['./housing-location.component.css'],
})
接著,在 DetailsComponent,我們使用 ActivatedRoute 服務取得 id ,再透過先前建立的 HousingService 去取得該 id 的房屋資料,在 template 之中作呈現。
import { Component, inject } from "@angular/core";
import { CommonModule } from "@angular/common";
import { ActivatedRoute } from "@angular/router"; // 取得當前路徑的變數
import { HousingService } from "../housing.service";
import { HousingLocation } from "../housinglocation";
@Component({
selector: "app-details",
standalone: true,
imports: [CommonModule],
template: `
<article>
<img
class="listing-photo"
[src]="housingLocation?.photo"
alt="Exterior photo of {{ housingLocation?.name }}"
/>
<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>
</article>
`,
styleUrls: ["./details.component.css"],
})
export class DetailsComponent {
route: ActivatedRoute = inject(ActivatedRoute); // 注入
housingService = inject(HousingService);
housingLocation: HousingLocation | undefined;
constructor() {
const housingLocationId = Number(this.route.snapshot.params["id"]);
this.housingLocation =
this.housingService.getHousingLocationById(housingLocationId);
}
}
?. 使得我們的程式在 housingLocation 為 null 或是 undefined 的時候不會崩壞。
Ch12. Adding a form to your Angular app
接下來,要在 DetailsComponent ,建立表單,我們先在 housing.service.ts,建立收到表單的函式,收到資料會在 console 呈現。
submitApplication(firstName: string, lastName: string, email: string) {
console.log(`Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`);
}
在 DetailsComponent 中使用表單相關功能和模組,建立 applyForm 和 submitApplication 去呼叫提交表單的函式
import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms";
@Component({
selector: "app-details",
standalone: true,
imports: [CommonModule, ReactiveFormsModule], // 模組
// ...
})
export class DetailsComponent {
// ...
applyForm = new FormGroup({
firstName: new FormControl(""),
lastName: new FormControl(""),
email: new FormControl(""),
});
constructor() {
// ...
}
submitApplication() {
this.housingService.submitApplication(
this.applyForm.value.firstName ?? "",
this.applyForm.value.lastName ?? "",
this.applyForm.value.email ?? ""
);
}
}
最後,則是在 template 使用 [formGroup]="applyForm" 和 (submit)=submitApplication(),值得注意的是 formControlName 裡面的值會對應到 this.applyForm.value 的值,所以要寫得和 submitApplication() 一致。
template: `
<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>
`;
Ch13. Add the search feature to your app
來到首頁,要做搜尋功能,依據 city 去做篩選,在 constructor 裡面加入 filteredLocationList,template 的地方改成用 filteredLocationList 去跑迴圈
template: `
<app-housing-location
*ngFor="let housingLocation of filteredLocationList"
[housingLocation]="housingLocation">
`
// ...
filteredLocationList: HousingLocation[] = [];
constructor() {
this.housingLocationList = this.housingService.getAllHousingLocations();
this.filteredLocationList = this.housingLocationList;
}
接著,我們在 input 的地方加上 #filter 的 template variable (樣板變數),並在下面的 button 加上 (click)="filterResults(filter.value)" 去針對 #filter 拿到的值做篩選。filterResults 函式去處理篩選,改變 filteredLocationList 來讓 HTML 產生畫面的變化。
template: `
<form>
<input type="text" placeholder="Filter by city" #filter>
<button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
</form>
`
// ...
filterResults(text: string) {
if (!text) {
this.filteredLocationList = this.housingLocationList;
}
this.filteredLocationList = this.housingLocationList.filter(
(housingLocation) =>
housingLocation?.city.toLowerCase().includes(text.toLocaleLowerCase())
);
}
Ch14. Add HTTP communication to your app
這章要模擬有 server 回傳資料,所以使用 json-server 的套件
npm install -g json-server
然後,在根目錄建立 db.json,裡面的資料是原本的 housingLocationList,然後這個 json-server 也需要透過 terminal 去啟動
json-server --watch db.json
目前畫面大概如下,我們在有 json-server 啟動的情況下,呼叫 http://localhost:3000/locations ,就會回傳我們之前定義的 housingLocationList 的資料。

接著,去 src/app/housing.service.ts 更改我們的取資料函式,getAllHousingLocations() 和 getHousingLocationById()
export class HousingService {
url = "http://localhost:3000/locations"; // fetch url
constructor() {}
async getAllHousingLocations(): Promise<HousingLocation[]> {
const data = await fetch(this.url);
return (await data.json()) ?? [];
}
async getHousingLocationById(
id: number
): Promise<HousingLocation | undefined> {
const data = await fetch(`${this.url}/${id}`);
return (await data.json()) ?? {};
}
submitApplication(firstName: string, lastName: string, email: string) {
console.log(
`Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`
);
}
}
然後,也要在使用那些函式的元件做修正,HomeComponent
constructor() {
this.housingService.getAllHousingLocations().then((
housingLocationList: HousingLocation[]) => {
this.housingLocationList = housingLocationList;
this.filteredLocationList = this.housingLocationList;
});
}
DetailsComponent
constructor() {
const housingLocationId = Number(this.route.snapshot.params['id']);
this.housingService
.getHousingLocationById(housingLocationId)
.then((housingLocation) => (this.housingLocation = housingLocation));
}
結論
這個教學總共花了我四小時實作和紀錄,從頭到尾了解基礎的 Angular 使用工具,因為我有些 Angular 開發經驗,所以花的時間會比較少,許多東西在之前做中學有使用過,但這種基礎的東西還是扎穩一點比較好,未來開發也會減少理解程式碼的時間。