🧱 Angular 基礎:模板語法、資料綁定與元件
📌 元件(Component)= TypeScript + HTML + CSS
Angular 的核心概念是元件。每個元件由三個部分組成:
// counter.component.ts — TypeScript 邏輯
import { Component } from '@angular/core';
@Component({
selector: 'app-counter', // HTML 標籤名稱
templateUrl: './counter.component.html', // 模板檔案
styleUrls: ['./counter.component.css'] // 樣式檔案
})
export class CounterComponent {
count = 0; // 元件的資料(屬性)
increment() { // 元件的方法
this.count++;
}
decrement() {
this.count--;
}
}
<!-- counter.component.html — 模板 -->
<div class="counter">
<h2>計數器</h2>
<p>目前數值:{{ count }}</p>
<button (click)="increment()">+1</button>
<button (click)="decrement()">-1</button>
</div>
💡 注意:
@Component是一個裝飾器(Decorator),這是 TypeScript 的功能,原生 JS 沒有。
📌 模板語法
插值(Interpolation){{ }}
<!-- 顯示元件屬性的值 -->
<h1>{{ title }}</h1>
<p>歡迎,{{ userName }}!你有 {{ messageCount }} 則訊息。</p>
<!-- 也可以放表達式 -->
<p>總價:{{ price * quantity }} 元</p>
<p>狀態:{{ isActive ? '啟用' : '停用' }}</p>
屬性綁定(Property Binding)[property]
<!-- 將元件屬性綁定到 HTML 屬性 -->
<img [src]="imageUrl" [alt]="imageDescription">
<button [disabled]="isLoading">送出</button>
<div [class.active]="isSelected">項目</div>
<div [style.color]="textColor">彩色文字</div>
事件綁定(Event Binding)(event)
<!-- 監聽 DOM 事件,觸發元件方法 -->
<button (click)="onSubmit()">送出</button>
<input (input)="onInputChange($event)">
<form (submit)="onFormSubmit($event)">
<div (mouseover)="onHover()" (mouseleave)="onLeave()">
雙向綁定(Two-way Binding)[(ngModel)]
// 需要先匯入 FormsModule
import { FormsModule } from '@angular/forms';
@Component({
// ...
imports: [FormsModule] // Standalone component 直接匯入
})
export class SearchComponent {
searchTerm = '';
}
<!-- 輸入框和資料自動雙向同步 -->
<input [(ngModel)]="searchTerm" placeholder="搜尋...">
<p>你正在搜尋:{{ searchTerm }}</p>
<!-- 當你打字時,searchTerm 自動更新;當程式改 searchTerm 時,輸入框也自動更新 -->
📌 結構指令
*ngIf — 條件顯示
<div *ngIf="isLoggedIn">
歡迎回來,{{ userName }}!
</div>
<div *ngIf="items.length > 0; else emptyTemplate">
共 {{ items.length }} 個項目
</div>
<ng-template #emptyTemplate>
<p>目前沒有任何項目。</p>
</ng-template>
*ngFor — 迴圈渲染
<ul>
<li *ngFor="let item of items; let i = index; trackBy: trackById">
{{ i + 1 }}. {{ item.name }} - {{ item.price }} 元
</li>
</ul>
// 元件中的 trackBy 函式(效能優化)
trackById(index: number, item: any): number {
return item.id;
}
📌 @Input() 和 @Output() 裝飾器
@Input() — 父元件傳資料給子元件
// child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>收到訊息:{{ message }}</p>`
})
export class ChildComponent {
@Input() message = ''; // 從父元件接收資料
}
<!-- parent.component.html -->
<app-child [message]="'Hello from parent!'"></app-child>
<app-child [message]="dynamicMessage"></app-child>
@Output() — 子元件發送事件給父元件
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `<button (click)="sendMessage()">通知父元件</button>`
})
export class ChildComponent {
@Output() notify = new EventEmitter<string>();
sendMessage() {
this.notify.emit('子元件說 Hello!');
}
}
<!-- parent.component.html -->
<app-child (notify)="onChildNotify($event)"></app-child>
// parent.component.ts
onChildNotify(message: string) {
console.log('收到子元件訊息:', message);
}
📌 完整範例:待辦清單
// todo.component.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
interface Todo {
id: number;
text: string;
done: boolean;
}
@Component({
selector: 'app-todo',
standalone: true,
imports: [FormsModule, CommonModule],
template: `
<h2>待辦清單</h2>
<div>
<input [(ngModel)]="newTodo" placeholder="新增待辦..." (keyup.enter)="addTodo()">
<button (click)="addTodo()" [disabled]="!newTodo.trim()">新增</button>
</div>
<ul>
<li *ngFor="let todo of todos" [class.done]="todo.done">
<input type="checkbox" [(ngModel)]="todo.done">
{{ todo.text }}
<button (click)="removeTodo(todo.id)">刪除</button>
</li>
</ul>
<p>完成 {{ completedCount }} / {{ todos.length }} 項</p>
`
})
export class TodoComponent {
newTodo = '';
todos: Todo[] = [];
private nextId = 1;
get completedCount(): number {
return this.todos.filter(t => t.done).length;
}
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({ id: this.nextId++, text: this.newTodo.trim(), done: false });
this.newTodo = '';
}
}
removeTodo(id: number) {
this.todos = this.todos.filter(t => t.id !== id);
}
}
🎯 注意:上面的
interface Todo是 TypeScript 語法,原生 JS 沒有介面(interface)的概念。 TypeScript 的型別系統讓你在開發時就能發現錯誤,而不是在執行時才出問題。
📌 小結
- 元件 = TypeScript(邏輯)+ HTML(模板)+ CSS(樣式)
- 插值
{{ }}顯示資料,[屬性]綁定屬性,(事件)監聽事件 [(ngModel)]實現雙向綁定*ngIf和*ngFor控制模板結構@Input()父傳子,@Output()子傳父