第 15 天- Add a Coffee Plan Form
在第15天,我擴展了 PlanPicker
組件,加入了一個 AddCoffeePlan
組件,用來向咖啡計劃列表中 (plan list) 添加新的咖啡計劃 (coffee plan)。接著,PlanPicker
組件有兩個子組件,分別是 AddCoffeePlan
和 CoffeePlan
。
建立一個新的 components/AddCoffeePlan.vue
檔案。
<style scoped>
input {
padding: 0.5rem 0.75rem;
}
.add-plan-form {
display: flex;
align-items: center;
justify-content: space-between;
}
.add-plan-form input {
width: 70%;
border-radius: 3px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #f1f5f8;
color: #606f7b;
padding: 0.5rem 0.75rem;
box-sizing: border-box;
font-size: 1rem;
letter-spacing: 0.5px;
margin: 0.5rem 0;
}
</style>
為 AddCoffeePlan
添加範圍限定的樣式。script
標籤具有 scoped
屬性。
<script setup lang="ts">
import { ref } from 'vue';
const emit = defineEmits<{
(e: 'newCoffeePlan', name: string): void
}>();
const newPlan = ref('');
function addPlan() {
const trimmedPlan = newPlan.value.trim();
if (!trimmedPlan) {
return;
}
emit('newCoffeePlan', trimmedPlan);
newPlan.value = '';
}
</script>
newPlan
這個 ref 用來儲存將要新增到咖啡計劃列表 (plan list) 中的新咖啡計劃 (coffee plan)。
defineEmits
定義了一個自訂的 newCoffeePlan
事件,用來將 newPlan
的值傳遞給 PlanPicker
組件。
addPlan
用來處理表單提交事件,觸發 newCoffeePlan
事件,並清空 newPlan
。
<template>
<form class="add-plan-form" @submit.prevent="addPlan">
<input v-model.trim="newPlan" type="text" placeholder="Add a new plan" />
<button class="btn btn-primary" type="submit" :disabled="newPlan.length < 5">
Add Plan
</button>
</form>
</template>
模板使用 v-model.trim
將文本框綁定到 newPlan
這個 ref。
除非使用者在文本框輸入至少5個字元,否則 Add Plan
按鈕會被禁用。
當使用者提交表單時,addPlan
函數會驗證新的咖啡計劃 (coffee plan),並將該值發送給父組件。
建立一個新的 lib/add-coffee-plan.svelte
檔案。
為 AddCoffeePlan
添加範圍限定 (scoped) 的樣式 (CSS)。 樣式 (CSS) 預設即為範圍限定 (scoped)。
<style>
input {
padding: 0.5rem 0.75rem;
}
.add-plan-form {
display: flex;
align-items: center;
justify-content: space-between;
}
.add-plan-form input {
width: 70%;
border-radius: 3px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #f1f5f8;
color: #606f7b;
padding: 0.5rem 0.75rem;
box-sizing: border-box;
font-size: 1rem;
letter-spacing: 0.5px;
margin: 0.5rem 0;
}
</style>
<script lang="ts">
interface Props {
addCoffeePlan: (plan: string) => void;
}
const { addCoffeePlan }: Props = $props();
let newPlan = $state('');
const addPlan = (e: SubmitEvent) => {
e.preventDefault();
const trimmedPlan = newPlan.trim();
if (!trimmedPlan) {
return;
}
addCoffeePlan(trimmedPlan);
newPlan = '';
}
</script>
newPlan
變數用來儲存將要新增到咖啡計劃列表 (plan list) 中的新咖啡計劃 (coffee plan)。
PlanPicker
組件將 addCoffeePlan
函數傳遞給 AddCoffeePlan
組件。 AddCoffeePlan
組件從 $props
解構出addCoffeePlan
函數,此函數用於向 PlanPicker
組件發出新的咖啡計劃 (coffee plan)。
addPlan
負責以下事項:
addCoffeePlan
函數通知父組件,<form class="add-plan-form" onsubmit={addPlan}>
<input type="text" placeholder="Add a new plan" bind:value={newPlan} />
<button type="submit" class="btn btn-primary" disabled={newPlan.length < 5}>
Add Plan
</button>
</form>
模板使用 bind:value
將文本框綁定到 newPlan
這個變數。
除非使用者在文本框輸入至少5個字元,否則 Add Plan
按鈕會被禁用。
表單註冊了 addPlan
用來監聽 onsubmit
事件。當使用者提交表單時,addPlan
會使用 addCoffeePlan
函數將新值傳遞給父組件。
Use the Angular CLI to create a new AddCoffeePlanComponent
ng g c AddCoffeePlan
該組件的 CSS 定義在獨立的 add-coffee-plan.component.css
檔案中。也支援內嵌的 CSS 樣式,具體使用哪種方式取決於程式設計的。
input {
padding: 0.5rem 0.75rem;
}
.add-plan-form {
display: flex;
align-items: center;
justify-content: space-between;
}
.add-plan-form input {
width: 70%;
border-radius: 3px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #f1f5f8;
color: #606f7b;
padding: 0.5rem 0.75rem;
box-sizing: border-box;
font-size: 1rem;
letter-spacing: 0.5px;
margin: 0.5rem 0;
}
import { ChangeDetectionStrategy, Component, output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-add-coffee-plan',
imports: [FormsModule],
template: `... inline template ...`,
styleUrl: './add-coffee-plan.component.css',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AddCoffeePlanComponent {
newPlan = signal('');
addCoffeePlan = output<string>();
addPlan() {
this.addCoffeePlan.emit(this.newPlan());
this.newPlan.set('');
}
}
該組件匯入了 FormsModule
,以啟用 ngSubmit
並使信號 (signal) 可以雙向綁定 (two-way binding) 到 HTML 元素。
newPlan
信號用來儲存將要新增到咖啡計劃列表 (plan list) 中的新咖啡計劃 (coffee plan)。
addCoffeePlan
是一個輸出函數,用於將新值發送給 PlanPicker
組件。
addPlan
方法處理 ngSubmit
事件,隱式阻止頁面重新加載,觸發addCoffeePlan
自訂事件,並清空 newPlan
信號。該組件匯入了 FormsModule
,以啟用 ngSubmit
並使信號 (signal) 可以雙向綁定到 HTML 元素。
template: `
<form class="add-plan-form" (ngSubmit)="addPlan()">
<input name="newPlan" type="text" placeholder="Add a new plan" [(ngModel)]="newPlan" />
<button class="btn btn-primary" type="submit" [disabled]="newPlan().length < 5">
Add Plan
</button>
</form>
`
[(ngModel)]
雙向綁定 newPlan
信號到輸入框。當使用者在文字框輸入新的咖啡計劃 (coffee plan) 時,newPlan
會接收新的值。
Add Plan
按鈕使用方括號語法 [expression]
將輸入綁定到屬性。當使用者輸入少於 5 個字元到文字框時,[disabled]
為 true。
ngSubmit
是一個自訂的 Angular 事件,當表單提交時觸發,事件處理函式 (event handler) 為 addPlan
。addPlan
呼叫 addCoffeePlan
輸出事件 (event emitter),將新值傳遞給父組件。
將 AddCoffeePlan
匯入到 PlanPicker
<script setup lang="ts">
import AddCoffeePlan from './AddCoffeePlan.vue'
</script>
在模板中使用 AddCoffeePlan
組件
<template>
<div class="plans">
<AddCoffeePlan @newCoffeePlan="(plan) => plans.push(plan)" />
</div>
</template>
@newCoffeePlan
自訂事件會呼叫一個內聯函式,以將新的計劃 (coffee plan) 加入咖啡計劃列表 (plan list) 中。
Import AddCoffeePlan
to PlanPicker
<script lang="ts">
import CoffeePlan from './coffee-plan.svelte';
</script>
Use the AddCoffeePlan
in the template
<div class="plans">
<AddCoffeePlan addCoffeePlan={(plan) => plans.push(plan)} />
</div>
addCoffeePlan
自訂事件也會呼叫一個內聯函式,將新的計劃 (coffee plan) 加入到咖啡計劃列表 (plan list) 中。
請將 AddCoffeePlanComponent
匯入到 PlanPickerComponent
中
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { AddCoffeePlanComponent } from '../add-coffee-plan/add-coffee-plan.component';
import { CoffeePlanComponent } from '../coffee-plan/coffee-plan.component';
@Component({
selector: 'app-plan-picker',
imports: [CoffeePlanComponent, AddCoffeePlanComponent],
template: `... inline template ...`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PlanPickerComponent {
plans = signal(['The Single', 'The Curious', 'The Addict', 'The Hacker']);
addPlan(name: string) {
this.plans.update((plans) => [...plans, name]);
}
}
將 AddCoffeePlanComponent
組件加入到 imports
陣列中。
addPlan
方法使用 Signal API 的 update
方法,將新的咖啡計劃加入到 plans
信號中。
<div class="plans">
<app-add-coffee-plan (addCoffeePlan)="addPlan($event)" />
</div>
addCoffeePlan
自訂事件使用特殊的 ``$event來傳遞新的咖啡計劃給
addPlan` 方法,以便將新的咖啡名稱加入。
我們已成功建立了一個 AddCoffeePlan
組件,該組件可以將新的咖啡計劃新增到 PlanPicker
組件中的咖啡計劃列表。