[{"data":1,"prerenderedAt":3870},["ShallowReactive",2],{"all-posts":3,"post:\u002Fithome-2026-day-13":1210},[4,23,35,46,57,68,80,91,102,114,126,138,149,160,171,182,193,204,215,226,237,248,258,268,278,288,298,308,319,330,341,354,368,380,390,400,411,421,431,441,451,461,471,481,491,501,511,521,531,541,551,561,571,581,591,601,611,621,631,641,651,661,675,691,704,719,731,742,752,762,772,782,792,802,812,822,832,842,852,862,871,881,891,901,911,921,931,941,951,961,971,980,990,1000,1010,1020,1036,1050,1062,1073,1083,1095,1107,1121,1134,1145,1156,1167,1180,1191,1201],{"path":5,"title":6,"description":7,"date":8,"updated":9,"tags":10,"categories":14,"draft":15,"wordCount":16,"readingTime":17,"canonical":18,"series":19},"\u002Fithome-2026-day-30","Day 30：Angular Router Providers 與生命週期管理，路由離開後 Service 會銷毀嗎？","平常在 Angular 中，我們可以透過 providers 決定 Service 的注入範圍。例如註冊在 Root，讓整個 Application 共用同一個實例；或註冊在 Component，讓 Service 隨著…","2026-10-14T14:46:39+08:00",null,[11,12,13],"iThome 鐵人賽","2026 鐵人賽","Angular",[11,13],false,7227,7,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10424222",{"name":20,"url":21,"day":22},"2026 iThome 鐵人賽：Angular 22 Signal 進化論","https:\u002F\u002Fithelp.ithome.com.tw\u002Fusers\u002F20116554\u002Fironman\u002F9939",30,{"path":24,"title":25,"description":26,"date":27,"updated":9,"tags":28,"categories":29,"draft":15,"wordCount":30,"readingTime":31,"canonical":32,"series":33},"\u002Fithome-2026-day-29","Day 29：Angular 也有 VueUse？使用 Signality 整合常見 Browser API","在 Vue 生態系中，VueUse 提供許多 Composables，讓我們可以快速使用瀏覽器功能，例如監聽網路狀態、保存使用者設定，或偵測元素是否進入可視範圍。","2026-10-13T23:14:18+08:00",[11,12,13],[11,13],6387,6,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10424070",{"name":20,"url":21,"day":34},29,{"path":36,"title":37,"description":38,"date":39,"updated":9,"tags":40,"categories":41,"draft":15,"wordCount":42,"readingTime":31,"canonical":43,"series":44},"\u002Fithome-2026-day-28","Day 28：Angular 動態建立 Component，NgComponentOutlet 與 ViewContainerRef","平常在 Angular 中使用 Component，通常會直接在 Template 裡面宣告：","2026-10-12T17:27:41+08:00",[11,12,13],[11,13],6565,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10423641",{"name":20,"url":21,"day":45},28,{"path":47,"title":48,"description":49,"date":50,"updated":9,"tags":51,"categories":52,"draft":15,"wordCount":53,"readingTime":17,"canonical":54,"series":55},"\u002Fithome-2026-day-27","Day 27：NgRx Store vs SignalStore，從 Action、Reducer 到直接更新 State","前幾天我們使用 NgRx SignalStore 實作購物車，透過 withState() 管理商品資料、withComputed() 計算總金額，再利用 withMethods() 與 patchState() 更新 …","2026-10-11T20:06:41+08:00",[11,12,13],[11,13],7308,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10423362",{"name":20,"url":21,"day":56},27,{"path":58,"title":59,"description":60,"date":61,"updated":9,"tags":62,"categories":63,"draft":15,"wordCount":64,"readingTime":17,"canonical":65,"series":66},"\u002Fithome-2026-day-26","Day 26：SignalStore 與 Strategy Pattern，透過 DI 動態切換業務邏輯","昨天我們使用 Angular 原生 Signal 與 NgRx SignalStore 實作購物車，透過 State 管理商品資料，再利用 Computed 計算商品數量與總金額。","2026-10-10T22:01:59+08:00",[11,12,13],[11,13],7347,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10423116",{"name":20,"url":21,"day":67},26,{"path":69,"title":70,"description":71,"date":72,"updated":9,"tags":73,"categories":74,"draft":15,"wordCount":75,"readingTime":76,"canonical":77,"series":78},"\u002Fithome-2026-day-25","Day 25：Angular SignalStore，原生 Signal 不夠用嗎？","在 Angular 開發中，如果資料只會被單一 Component 使用，通常可以直接透過 signal() 管理。但當多個 Component 需要共用同一份資料時，就得考慮狀態應該放在哪裡。","2026-10-09T21:28:45+08:00",[11,12,13],[11,13],9117,9,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10422784",{"name":20,"url":21,"day":79},25,{"path":81,"title":82,"description":83,"date":84,"updated":9,"tags":85,"categories":86,"draft":15,"wordCount":87,"readingTime":31,"canonical":88,"series":89},"\u002Fithome-2026-day-24","Day 24：SPA Router 為什麼不再只靠 History API？","平常使用 Angular Router 時，可以透過 routerLink 或 router.navigate() 切換不同頁面。","2026-10-08T14:30:47+08:00",[11,12,13],[11,13],6330,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10422348",{"name":20,"url":21,"day":90},24,{"path":92,"title":93,"description":94,"date":95,"updated":9,"tags":96,"categories":97,"draft":15,"wordCount":98,"readingTime":17,"canonical":99,"series":100},"\u002Fithome-2026-day-23","Day 23：RouteReuseStrategy，切換路由一定要銷毀 Component 嗎？","在後台系統中，很常會看到頁籤式介面，例如：","2026-10-07T15:58:32+08:00",[11,12,13],[11,13],7361,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10422016",{"name":20,"url":21,"day":101},23,{"path":103,"title":104,"description":105,"date":106,"updated":9,"tags":107,"categories":108,"draft":15,"wordCount":109,"readingTime":110,"canonical":111,"series":112},"\u002Fithome-2026-day-22","Day 22：Angular Render Lifecycle，從 afterNextRender 到 DestroyRef","過去在 Angular 中，只要遇到初始化、View 建立或資源清理，通常會先想到 Lifecycle Hooks：","2026-10-06T23:36:23+08:00",[11,12,13],[11,13],8111,8,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10421810",{"name":20,"url":21,"day":113},22,{"path":115,"title":116,"description":117,"date":118,"updated":9,"tags":119,"categories":120,"draft":15,"wordCount":121,"readingTime":122,"canonical":123,"series":124},"\u002Fithome-2026-day-21","Day 21：Angular Template 還適合呼叫 Method 嗎？從 Pipe、RxJS 到 computed","過去在寫 Angular 時，因為 Template 可以直接呼叫 Method，很容易在專案裡看到大量這樣的寫法。","2026-10-05T23:30:32+08:00",[11,12,13],[11,13],5431,5,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10421425",{"name":20,"url":21,"day":125},21,{"path":127,"title":128,"description":129,"date":130,"updated":9,"tags":131,"categories":132,"draft":15,"wordCount":133,"readingTime":134,"canonical":135,"series":136},"\u002Fithome-2026-day-20","Day 20：Signal Forms 非同步驗證，validateHttp 與 validateAsync","前兩篇介紹了 Signal Forms 的基本驗證，以及需要參考其他欄位的 Cross-field Validation。","2026-10-04T20:56:17+08:00",[11,12,13],[11,13],4278,4,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10420910",{"name":20,"url":21,"day":137},20,{"path":139,"title":140,"description":141,"date":142,"updated":9,"tags":143,"categories":144,"draft":15,"wordCount":145,"readingTime":134,"canonical":146,"series":147},"\u002Fithome-2026-day-19","Day 19：Signal Forms Cross-field Validation 與欄位更新時機","上一篇介紹 Signal Forms 時，已經看到可以透過 Schema 定義欄位驗證，例如 required()、email()、minLength()。","2026-10-03T23:18:26+08:00",[11,12,13],[11,13],3783,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10420631",{"name":20,"url":21,"day":148},19,{"path":150,"title":151,"description":152,"date":153,"updated":9,"tags":154,"categories":155,"draft":15,"wordCount":156,"readingTime":31,"canonical":157,"series":158},"\u002Fithome-2026-day-18","Day 18：Angular Signal Forms 穩定了，跟我們用了這麼久的 Reactive Forms 到底差在哪？","在 Angular 中處理表單，很多專案使用的都是 Reactive Forms。","2026-10-02T23:28:36+08:00",[11,12,13],[11,13],5886,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10420249",{"name":20,"url":21,"day":159},18,{"path":161,"title":162,"description":163,"date":164,"updated":9,"tags":165,"categories":166,"draft":15,"wordCount":167,"readingTime":31,"canonical":168,"series":169},"\u002Fithome-2026-day-17","Day 17：effect() 不是拿來同步 Signal 的，Angular 的 Side Effect 該怎麼寫？","effect() 是 Angular signal 裡很容易被誤用的一個 API。因為寫法直覺，只要讀取 signal，就能在值改變時執行一些事情，所以很常被拿來同步狀態、打 API，甚至再去修改其他 signal。","2026-10-01T21:22:13+08:00",[11,12,13],[11,13],6473,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10419779",{"name":20,"url":21,"day":170},17,{"path":172,"title":173,"description":174,"date":175,"updated":9,"tags":176,"categories":177,"draft":15,"wordCount":178,"readingTime":31,"canonical":179,"series":180},"\u002Fithome-2026-day-16","Day 16：即將登場的 Route Resources，Angular Router 為什麼需要新的資料載入方式？","過去在 Angular Router 中，如果希望進入 Component 前先完成資料載入，可以透過 Resolver，讓資料取得流程在 Component 初始化之前執行。","2026-09-30T23:12:05+08:00",[11,12,13],[11,13],6534,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10419409",{"name":20,"url":21,"day":181},16,{"path":183,"title":184,"description":185,"date":186,"updated":9,"tags":187,"categories":188,"draft":15,"wordCount":189,"readingTime":17,"canonical":190,"series":191},"\u002Fithome-2026-day-15","Day 15：搞懂 Injection Context，從 inject()、runInInjectionContext() 到 injectAsync()","前面提到 Injection Context 時，可能會讓人聯想到先前介紹過的 Reactive Context。我一開始也有點搞混，因此花了一些時間重新理解 Angular 裡這些不同的執行環境。","2026-09-29T13:44:28+08:00",[11,12,13],[11,13],6986,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10418752",{"name":20,"url":21,"day":192},15,{"path":194,"title":195,"description":196,"date":197,"updated":9,"tags":198,"categories":199,"draft":15,"wordCount":200,"readingTime":17,"canonical":201,"series":202},"\u002Fithome-2026-day-14","Day 14：Lazy Loading 不只一種，從 Router Preloading、@defer 到 injectAsync()","當 Client Side Rendering 的應用越來越大時，我們通常會開始思考，能不能把原本一次載入的內容拆得更小，等到真正需要時再下載，避免初始載入過多 JavaScript，拉長畫面呈現的時間。","2026-09-28T22:44:46+08:00",[11,12,13],[11,13],7674,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10418485",{"name":20,"url":21,"day":203},14,{"path":205,"title":206,"description":207,"date":208,"updated":9,"tags":209,"categories":210,"draft":15,"wordCount":211,"readingTime":110,"canonical":212,"series":213},"\u002Fithome-2026-day-13","Day 13：從 Router Event 到 Signal","路由狀態管理在前端開發中比較少被單獨拿出來討論，但在 Angular 裡，一次路由導航其實會經過不少階段，也會在過程中發出不同的 Router Event，例如 NavigationStart、NavigationEnd…","2026-09-27T20:37:55+08:00",[11,12,13],[11,13],7777,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10417948",{"name":20,"url":21,"day":214},13,{"path":216,"title":217,"description":218,"date":219,"updated":9,"tags":220,"categories":221,"draft":15,"wordCount":222,"readingTime":122,"canonical":223,"series":224},"\u002Fithome-2026-day-12","Day 12：從 httpResource 到 TanStack Query，認識 Server State","前面我們花了不少時間討論，為什麼「請求本身的狀態」和「元件的顯示狀態」應該分開處理。","2026-09-26T23:35:12+08:00",[11,12,13],[11,13],5119,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10417614",{"name":20,"url":21,"day":225},12,{"path":227,"title":228,"description":229,"date":230,"updated":9,"tags":231,"categories":232,"draft":15,"wordCount":233,"readingTime":122,"canonical":234,"series":235},"\u002Fithome-2026-day-11","Day 11：用 rxResource 串起 Observable 與 Resource","前面介紹了不少 Resource 到 httpResource() 的應用。不過在 Angular 開發中，Observable 與 RxJS 仍然是很常見的非同步資料處理方式，像是 switchMap、map、debo…","2026-09-25T22:42:04+08:00",[11,12,13],[11,13],4908,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10417143",{"name":20,"url":21,"day":236},11,{"path":238,"title":239,"description":240,"date":241,"updated":9,"tags":242,"categories":243,"draft":15,"wordCount":244,"readingTime":31,"canonical":245,"series":246},"\u002Fithome-2026-day-10","Day 10：linkedSignal 讓相依狀態也能被修改","昨天我們透過 httpResource() 搭配 chain() 處理兩個非同步資料之間的依賴。下游 Request 需要使用上游資料時，會等上游完成後再繼續；當 Request 的來源改變，Resource 的狀態也會…","2026-09-24T23:49:49+08:00",[11,12,13],[11,13],5623,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10416746",{"name":20,"url":21,"day":247},10,{"path":249,"title":250,"description":251,"date":252,"updated":9,"tags":253,"categories":254,"draft":15,"wordCount":255,"readingTime":134,"canonical":256,"series":257},"\u002Fithome-2026-day-09","Day 9：httpResource 實務篇，統一回覆格式與 chain 相依請求","昨天我們透過 Orval 串起 API Contract，了解如何根據 OpenAPI 規格產生前端需要的型別與 API 呼叫方式。","2026-09-23T23:42:40+08:00",[11,12,13],[11,13],4358,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10416227",{"name":20,"url":21,"day":76},{"path":259,"title":260,"description":261,"date":262,"updated":9,"tags":263,"categories":264,"draft":15,"wordCount":265,"readingTime":122,"canonical":266,"series":267},"\u002Fithome-2026-day-08","Day 8：用 Orval 串起 Angular API Contract，同時產生 HttpClient、httpResource 與 Zod","前後端在對接 API 時，常見的問題之一，就是雙方對資料格式的定義逐漸不一致。","2026-09-22T23:48:12+08:00",[11,12,13],[11,13],4451,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10415694",{"name":20,"url":21,"day":110},{"path":269,"title":270,"description":271,"date":272,"updated":9,"tags":273,"categories":274,"draft":15,"wordCount":275,"readingTime":17,"canonical":276,"series":277},"\u002Fithome-2026-day-07","Day 7：從 Resource 到 httpResource，用 Signal 管理 HTTP 資料請求","昨天我們提到，Resource API 本身是一套通用的非同步資料模型，比較適合用在「讀取資料」這類情境，而不是新增、修改或刪除資料這種 Mutation 操作。","2026-09-21T22:13:18+08:00",[11,12,13],[11,13],6735,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10415038",{"name":20,"url":21,"day":17},{"path":279,"title":280,"description":281,"date":282,"updated":9,"tags":283,"categories":284,"draft":15,"wordCount":285,"readingTime":122,"canonical":286,"series":287},"\u002Fithome-2026-day-06","Day 6：Resource API，把非同步資料帶進 Signal","今天來看 Angular Signal 裡一個很重要的非同步資料 API：Resource API。","2026-09-20T19:25:37+08:00",[11,12,13],[11,13],5054,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10414377",{"name":20,"url":21,"day":31},{"path":289,"title":290,"description":291,"date":292,"updated":9,"tags":293,"categories":294,"draft":15,"wordCount":295,"readingTime":134,"canonical":296,"series":297},"\u002Fithome-2026-day-05","Day 5：從 Reactive Graph 到 View Tree，看 Angular 如何找到真正需要更新的畫面","前面我們已經知道，當 Signal 的值發生變化後，Angular 會透過 Reactive Graph 找到受到影響的 Consumer。","2026-09-19T20:19:47+08:00",[11,12,13],[11,13],4364,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10413852",{"name":20,"url":21,"day":122},{"path":299,"title":300,"description":301,"date":302,"updated":9,"tags":303,"categories":304,"draft":15,"wordCount":305,"readingTime":122,"canonical":306,"series":307},"\u002Fithome-2026-day-04","Day 4：Angular 怎麼知道誰正在讀 Signal？從 Reactive Context 理解依賴追蹤","前面我們看過，當 Template 讀取 Signal 時，Angular 會記住「誰依賴了誰」；當 Signal 發生變化後，也會透過 Reactive Graph 通知相關的 Consumer。","2026-09-18T21:58:16+08:00",[11,12,13],[11,13],4894,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10413313",{"name":20,"url":21,"day":134},{"path":309,"title":310,"description":311,"date":312,"updated":9,"tags":313,"categories":314,"draft":15,"wordCount":315,"readingTime":122,"canonical":316,"series":317},"\u002Fithome-2026-day-03","Day 3：從 set()、update() 看懂 Angular Signal 的基本使用與更新機制","昨天我們先試著理解，在 Template 中使用 Signal() 時，Angular 是如何建立依賴關係，並在 Signal 發生變化後，知道哪些 View 可能受到影響。","2026-09-17T23:08:58+08:00",[11,12,13],[11,13],5451,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10412826",{"name":20,"url":21,"day":318},3,{"path":320,"title":321,"description":322,"date":323,"updated":9,"tags":324,"categories":325,"draft":15,"wordCount":326,"readingTime":134,"canonical":327,"series":328},"\u002Fithome-2026-day-02","Day 2：Angular Signal 是什麼？從 Change Detection 理解它的運作原理","傳統 Angular 在監測資料變化時，通常會搭配 zone.js。像是 DOM 事件、Promise、Timer、HTTP 請求這些非同步操作發生時，zone.js 會幫忙攔截。","2026-09-16T20:19:12+08:00",[11,12,13],[11,13],4276,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10411944",{"name":20,"url":21,"day":329},2,{"path":331,"title":332,"description":333,"date":334,"updated":9,"tags":335,"categories":336,"draft":15,"wordCount":337,"readingTime":329,"canonical":338,"series":339},"\u002Fithome-2026-day-01","Day 1：為什麼想聊 Angular Signal？","相信講到前端框架，大家第一時間想到的通常會是 Vue、React，甚至也有人會推薦 Svelte 這類近年討論度很高的框架。但一講到 Angular，很多人的第一印象可能就像看到一座大城堡一樣：東西很多、規則很多，一時之…","2026-09-15T23:56:26+08:00",[11,12,13],[11,13],1478,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10411425",{"name":20,"url":21,"day":340},1,{"path":342,"title":343,"description":344,"date":345,"updated":9,"tags":346,"categories":351,"draft":15,"wordCount":353,"readingTime":134,"canonical":9,"series":9},"\u002FTS-Excess-Property-Checks","TypeScript 踩坑筆記：物件字面量的多餘屬性檢查機制","深入探討 TypeScript 的多餘屬性檢查（Excess Property Checks）機制，解析物件字面量與變數賦值的型別檢查差異，並提供實務開發中避免型別陷阱的最佳實踐方法。","2026-01-31T16:29:23+08:00",[347,348,349,350],"Excess Property Checks","Structural Typing","Type Safety","NestJS",[352],"TypeScript",3551,{"path":355,"title":356,"description":357,"date":358,"updated":9,"tags":359,"categories":361,"draft":15,"wordCount":363,"readingTime":329,"canonical":364,"series":365},"\u002Fithome-2025-day-30","Day 30：開發之旅完結 - 感謝與展望","終於來到鐵人賽的最後一天！這是我第二次參加鐵人賽，今年的主題是 LINE Bot 開發。這個主題源自於我參加的十二週讀書會分享，當時我發現大家對 LINE Bot 很感興趣，而我自己也一直很喜歡這個主題，於是早在六月就定…","2025-10-13T20:11:12+08:00",[11,360],"2025 鐵人賽",[11,362],"Line Bot",1751,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10396850",{"name":366,"url":367,"day":22},"2025 iThome 鐵人賽：Line Bot × NestJS：30 天開發日記","https:\u002F\u002Fithelp.ithome.com.tw\u002Fusers\u002F20116554\u002Fironman\u002F8779",{"path":369,"title":370,"description":371,"date":372,"updated":9,"tags":373,"categories":376,"draft":15,"wordCount":377,"readingTime":31,"canonical":378,"series":379},"\u002Fithome-2025-day-29","Day 29：LIFF 會員註冊 - 信箱驗證與 Swagger 文件整合","延續 Day 28 會員註冊的流程，在實際的會員註冊場景中，電子信箱驗證是確保用戶身份真實性的重要環節。本篇將基於昨天的註冊流程，新增兩個核心 API：發送驗證信與驗證驗證碼，讓會員註冊功能更加完整且安全。","2025-10-12T23:15:00+08:00",[11,360,362,374,350,375],"LIFF","Swagger",[11,362],6089,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10396512",{"name":366,"url":367,"day":34},{"path":381,"title":382,"description":383,"date":384,"updated":9,"tags":385,"categories":386,"draft":15,"wordCount":387,"readingTime":122,"canonical":388,"series":389},"\u002Fithome-2025-day-28","Day 28：LIFF 會員註冊 - ID Token 驗證與資料庫整合","今天的開發重點是建立會員卡系統，將 LIFF 前端與 NestJS 後端完整串接。","2025-10-11T22:57:29+08:00",[11,360,362,374,350],[11,362],5254,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10396146",{"name":366,"url":367,"day":45},{"path":391,"title":392,"description":393,"date":394,"updated":9,"tags":395,"categories":396,"draft":15,"wordCount":397,"readingTime":318,"canonical":398,"series":399},"\u002Fithome-2025-day-27","Day 27：群組事件與檔案訊息接收","今天主要補充 LINE Bot 在群組中的專屬特性，包括群組限定的 Webhook 事件，以及前面提到但尚未說明的第 7 種訊息類型 file 檔案訊息。","2025-10-10T21:01:52+08:00",[11,360,362,350],[11,362],2888,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10395785",{"name":366,"url":367,"day":56},{"path":401,"title":402,"description":403,"date":404,"updated":9,"tags":405,"categories":407,"draft":15,"wordCount":408,"readingTime":122,"canonical":409,"series":410},"\u002Fithome-2025-day-26","Day 26：LINE Pay 線上付款","LINE Pay 提供兩種付款方式：線上付款與實體付款。實體付款主要採用店家出示條碼的模式，顧客掃描後即可輸入金額完成付款。這兩種付款方式都支援在沙盒(Sandbox)環境中進行測試。本文將著重說明線上付款的整合流程。","2025-10-09T23:39:32+08:00",[11,360,406],"Line Pay",[11,362],4876,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10395481",{"name":366,"url":367,"day":67},{"path":412,"title":413,"description":414,"date":415,"updated":9,"tags":416,"categories":417,"draft":15,"wordCount":418,"readingTime":318,"canonical":419,"series":420},"\u002Fithome-2025-day-25","Day 25：優惠券設定與 Coupon Message 實作","LINE Official Account 的「優惠券」是提升顧客互動與忠誠度的重要功能。透過獎勵機制，不僅能吸引新客，更能有效促進回購。","2025-10-08T23:49:32+08:00",[11,360,362,350],[11,362],3207,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10395076",{"name":366,"url":367,"day":79},{"path":422,"title":423,"description":424,"date":425,"updated":9,"tags":426,"categories":427,"draft":15,"wordCount":428,"readingTime":134,"canonical":429,"series":430},"\u002Fithome-2025-day-24","Day 24：LIFF 刮刮樂實戰體驗","在了解 LIFF 的基礎設計流程後，本文將透過「刮刮樂」案例示範 LIFF API 的實際應用。從啟動時檢查權限與執行環境、確認用戶分享功能權限，到刮刮樂結束後發送訊息並關閉 LIFF，完整呈現 LIFF 在互動應用中的…","2025-10-07T22:36:58+08:00",[11,360,362,374],[11,362],4200,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10394618",{"name":366,"url":367,"day":90},{"path":432,"title":433,"description":434,"date":435,"updated":9,"tags":436,"categories":437,"draft":15,"wordCount":438,"readingTime":134,"canonical":439,"series":440},"\u002Fithome-2025-day-23","Day 23：LIFF 網頁應用開發入門","在鐵人賽前半段，我們深入探討了 LINE Bot 的兩大核心功能：Richmenu(選單設置)與各種 Reply Message(回覆訊息)的訊息類型。然而在實務應用中，若要提供更豐富的互動體驗，Line 往往需要搭配 …","2025-10-06T23:06:57+08:00",[11,360,362,374],[11,362],4400,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10394035",{"name":366,"url":367,"day":101},{"path":442,"title":443,"description":444,"date":445,"updated":9,"tags":446,"categories":447,"draft":15,"wordCount":448,"readingTime":329,"canonical":449,"series":450},"\u002Fithome-2025-day-22","Day 22：Render 雲端平台部署 Nest 後端伺服器","今天的目標是將系統正式上線。經過這段時間的開發，我們已完成 Line Message API 的核心功能與日誌處理機制。","2025-10-05T22:52:11+08:00",[11,360,362,350],[11,362],1895,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10393809",{"name":366,"url":367,"day":113},{"path":452,"title":453,"description":454,"date":455,"updated":9,"tags":456,"categories":457,"draft":15,"wordCount":458,"readingTime":318,"canonical":459,"series":460},"\u002Fithome-2025-day-21","Day 21：Nest Pino 整合 Grafana Loki 日誌","Loki 環境變數可參考 Day 20 文章的 Grafana Data Source settings 設定","2025-10-04T17:24:26+08:00",[11,360,362,350],[11,362],2740,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10393305",{"name":366,"url":367,"day":125},{"path":462,"title":463,"description":464,"date":465,"updated":9,"tags":466,"categories":467,"draft":15,"wordCount":468,"readingTime":318,"canonical":469,"series":470},"\u002Fithome-2025-day-20","Day 20：Grafana Loki 日誌服務 - 從申請到 API 串接測試","目前已經理解了 LINE Messaging API 的核心功能，包括如何接收訊息、串接第三方服務，以及發送多種類型的訊息。接下來，我們將把原本架設在 ngrok 的後端伺服器，透過 GitHub 自動化部署到 Rend…","2025-10-03T23:16:16+08:00",[11,360,362,350],[11,362],2461,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10392910",{"name":366,"url":367,"day":137},{"path":472,"title":473,"description":474,"date":475,"updated":9,"tags":476,"categories":477,"draft":15,"wordCount":478,"readingTime":318,"canonical":479,"series":480},"\u002Fithome-2025-day-19","Day 19：Flex Message Carousel 食譜分享","Flex Message 可以分成 bubble(單張卡片) 跟 carousel(多張卡片輪播) 兩種方式，昨天示範了 bubble 格式的電子個人名片後，今天主要展示 carousel 的應用，兩者的基本結構大致相同…","2025-10-02T16:06:17+08:00",[11,360,362,350],[11,362],2267,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10392256",{"name":366,"url":367,"day":148},{"path":482,"title":483,"description":484,"date":485,"updated":9,"tags":486,"categories":487,"draft":15,"wordCount":488,"readingTime":122,"canonical":489,"series":490},"\u002Fithome-2025-day-18","Day 18：Flex Message 設計個人名片","從基礎的文字互動開始，我們逐步探索了六大核心訊息類型的發送機制、Template Message 的結構化設計，以及 ImageMap Message 的互動式體驗。今天，我們將進入 LINE Bot 最具彈性的訊息格式…","2025-10-01T17:57:47+08:00",[11,360,362,350],[11,362],4468,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10391723",{"name":366,"url":367,"day":159},{"path":492,"title":493,"description":494,"date":495,"updated":9,"tags":496,"categories":497,"draft":15,"wordCount":498,"readingTime":122,"canonical":499,"series":500},"\u002Fithome-2025-day-17","Day 17：ImageMap Message  從互動海報到影片嵌入","在 Day 16 文章中，我們使用 Cloudflare Workers 實作了圖片代理功能。到目前為止，我們已經完成以下準備工作:","2025-09-30T11:58:32+08:00",[11,360,362,350],[11,362],4807,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10391164",{"name":366,"url":367,"day":170},{"path":502,"title":503,"description":504,"date":505,"updated":9,"tags":506,"categories":507,"draft":15,"wordCount":508,"readingTime":134,"canonical":509,"series":510},"\u002Fithome-2025-day-16","Day 16：Cloudflare Workers 圖片代理實作","在 Day 15 文章中，我們處理了 ImageMap Message 的背景圖，接著我們遇到了圖片不能出現副檔名及動態路徑支援不同解析度的問題。今天主要會透過 Cloudflare Workers 來解決這兩個問題，讓…","2025-09-29T16:15:41+08:00",[11,360,362,350],[11,362],3412,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10390567",{"name":366,"url":367,"day":181},{"path":512,"title":513,"description":514,"date":515,"updated":9,"tags":516,"categories":517,"draft":15,"wordCount":518,"readingTime":134,"canonical":519,"series":520},"\u002Fithome-2025-day-15","Day 15：共通屬性處理優化及 Imagemap  圖片製作","針對 Day 14 的程式碼架構進行優化。由於 Sender 和 Quick Reply 功能在所有回覆訊息類型中都能使用，我們應該將這些共用屬性抽取為獨立函數進行統一處理。","2025-09-28T21:03:38+08:00",[11,360,362,350],[11,362],4033,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10389697",{"name":366,"url":367,"day":192},{"path":522,"title":523,"description":524,"date":525,"updated":9,"tags":526,"categories":527,"draft":15,"wordCount":528,"readingTime":31,"canonical":529,"series":530},"\u002Fithome-2025-day-14","Day 14：LINE Bot Template Message 訊息回覆","經過前幾天的學習，我們已經成功掌握了 LINE Bot 六大基礎訊息類型的發送技巧，為訊息互動奠定了穩固的基礎。今天，我們將踏入更具挑戰性的進階應用領域！","2025-09-27T15:54:06+08:00",[11,360,362,350],[11,362],6372,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10388976",{"name":366,"url":367,"day":203},{"path":532,"title":533,"description":534,"date":535,"updated":9,"tags":536,"categories":537,"draft":15,"wordCount":538,"readingTime":122,"canonical":539,"series":540},"\u002Fithome-2025-day-13","Day 13：LINE Bot 客製化 Sender 與 Quick Reply 功能實作","LINE Bot 所有回覆訊息類型都具備「共用屬性」功能，這些屬性帶來兩項重要的互動體驗提升：","2025-09-26T11:41:34+08:00",[11,360,362,350],[11,362],4426,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10388037",{"name":366,"url":367,"day":214},{"path":542,"title":543,"description":544,"date":545,"updated":9,"tags":546,"categories":547,"draft":15,"wordCount":548,"readingTime":318,"canonical":549,"series":550},"\u002Fithome-2025-day-12","Day 12：LINE Bot Image、Video、Audio、Location 訊息回覆","Day 11 文章中，我們針對 LINE Bot 可以回覆的Text及Sticker基礎型別進行了封裝，不僅新增了 line-message.ts模組，還讓開發者可以用最少的參數產生 LINE Message 所需要的格…","2025-09-25T18:08:09+08:00",[11,360,362,350],[11,362],2977,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10387598",{"name":366,"url":367,"day":225},{"path":552,"title":553,"description":554,"date":555,"updated":9,"tags":556,"categories":557,"draft":15,"wordCount":558,"readingTime":31,"canonical":559,"series":560},"\u002Fithome-2025-day-11","Day 11：LINE Bot Text Emoji 與 Sticker 訊息回覆","目前我們的 LINE Bot 主要使用 Text 方式回覆訊息。今天將探討 Text 類型的 Emoji 應用，以及 Sticker 回覆方式，基於 Day 8 版本進行改寫。","2025-09-24T23:21:11+08:00",[11,360,350,362],[11,362],5659,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10386993",{"name":366,"url":367,"day":236},{"path":562,"title":563,"description":564,"date":565,"updated":9,"tags":566,"categories":567,"draft":15,"wordCount":568,"readingTime":318,"canonical":569,"series":570},"\u002Fithome-2025-day-10","Day 10：LINE Rich Menu Switch Action 提升使用者體驗","在 Day 9 文章中，我們結合 LINE 平台的功能，將天氣服務與圖片上傳服務整合，打造出第一張 LINE 圖文選單，透過 URI action 的方式觸發平台提供的互動效果。接著，我們使用客製化選單製作出 9 格的客…","2025-09-23T15:02:57+08:00",[11,360,362,350],[11,362],2906,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10386165",{"name":366,"url":367,"day":247},{"path":572,"title":573,"description":574,"date":575,"updated":9,"tags":576,"categories":577,"draft":15,"wordCount":578,"readingTime":318,"canonical":579,"series":580},"\u002Fithome-2025-day-09","Day  9：打造 LINE Rich Menu - 後台設計 vs API 客製化","在 Day8 文章中，我們透過整合雲端服務 Cloudinary，學習如何處理使用者上傳的圖片檔案，並將其儲存至雲端。然而，LINE Bot 除了讓使用者輸入文字回覆的方式之外，更常見的是透過圖文選單(Rich Menu…","2025-09-22T21:54:25+08:00",[11,360,362,350],[11,362],3285,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10385658",{"name":366,"url":367,"day":76},{"path":582,"title":583,"description":584,"date":585,"updated":9,"tags":586,"categories":587,"draft":15,"wordCount":588,"readingTime":17,"canonical":589,"series":590},"\u002Fithome-2025-day-08","Day 8：Cloudinary 雲端圖片儲存整合","在 Day 6 中，我們透過整合 OpenWeatherMap API 與座標位置訊息學習了如何結合第三方服務。今天將探索 Cloudinary 雲端服務，解決 LINE 圖片資源會過期的問題，學習如何透過 LINE M…","2025-09-21T23:13:19+08:00",[11,360,362,350],[11,362],7215,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10384968",{"name":366,"url":367,"day":110},{"path":592,"title":593,"description":594,"date":595,"updated":9,"tags":596,"categories":597,"draft":15,"wordCount":598,"readingTime":122,"canonical":599,"series":600},"\u002Fithome-2025-day-07","Day 7：Joi 環境變數統一驗證，重構 LINE Bot 天氣服務模組","在 Day 6 開發 LINE Bot 結合 OpenWeatherMap API 的過程中，我們採用私有方法將 API 請求處理與資料格式化整合在一起。今天將對 Day 6 的實作進行架構重構，主要目標是建立更完善的環…","2025-09-20T23:07:08+08:00",[11,360,362,350],[11,362],5157,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10384302",{"name":366,"url":367,"day":17},{"path":602,"title":603,"description":604,"date":605,"updated":9,"tags":606,"categories":607,"draft":15,"wordCount":608,"readingTime":134,"canonical":609,"series":610},"\u002Fithome-2025-day-06","Day 6：整合第三方 API 天氣查詢服務","Day 5 帶大家了解 LINE Bot 能接收使用者傳遞的「6+1」種訊息類型。之所以這樣強調，是因為 File 類型僅能在群組聊天中使用，這個限制我自己也經常忘記，所以習慣用「6+1」來提醒自己。","2025-09-19T21:07:47+08:00",[11,360,362,350],[11,362],3892,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10383636",{"name":366,"url":367,"day":31},{"path":612,"title":613,"description":614,"date":615,"updated":9,"tags":616,"categories":617,"draft":15,"wordCount":618,"readingTime":122,"canonical":619,"series":620},"\u002Fithome-2025-day-05","Day 5：整合 Pino Logger 提升 LINE Bot 專案的可維護性","看著專案逐漸成型，現在該開始重視日誌記錄的規劃了。在實際部署時，我們通常會區分 develop(開發環境)和 production(正式環境)兩種不同的執行環境，針對不同環境採用相應的日誌策略。","2025-09-18T23:30:00+08:00",[11,360,362,350],[11,362],4746,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10382298",{"name":366,"url":367,"day":122},{"path":622,"title":623,"description":624,"date":625,"updated":9,"tags":626,"categories":627,"draft":15,"wordCount":628,"readingTime":31,"canonical":629,"series":630},"\u002Fithome-2025-day-04","Day 4：掌握 LINE Bot 七種訊息類型與 NestJS 架構優化","目前我們都只使用 LINE Message Event 中的純文字(text)格式進行回覆，但除了純文字之外，LINE Bot 後端伺服器其實可以接收多種不同類型的訊息。今天就讓我們一起來深入了解這些訊息類型吧！","2025-09-17T22:53:37+08:00",[11,360,362,350],[11,362],5828,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10381651",{"name":366,"url":367,"day":134},{"path":632,"title":633,"description":634,"date":635,"updated":9,"tags":636,"categories":637,"draft":15,"wordCount":638,"readingTime":122,"canonical":639,"series":640},"\u002Fithome-2025-day-03","Day 3：Webhook 簽章驗證與訊息回覆策略(Push & Reply)","上一篇帶大家透過「Webhook 模式」成功架設了專屬的 LINE Bot 代理人。相信大家在體驗基礎功能後，對於 LINE Bot Webhook 還能監聽哪些事件、支援哪些類型的訊息回覆，以及如何確保後端收到的訊息確…","2025-09-16T21:53:39+08:00",[11,360,362,350],[11,362],5360,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10381303",{"name":366,"url":367,"day":318},{"path":642,"title":643,"description":644,"date":645,"updated":9,"tags":646,"categories":647,"draft":15,"wordCount":648,"readingTime":31,"canonical":649,"series":650},"\u002Fithome-2025-day-02","Day 2：從 Webhook 到後端，打造你的專屬機器人","上一篇，帶大家透過「Chat 模式」，讓自身變成客服機器人的方式。這篇會從「Webhook 模式」切入，並以 NestJS 作為後端框架，教你如何在休息時間，也能讓後端伺服器當你的替身幫你解惑使用者的問題。","2025-09-15T21:20:49+08:00",[11,360,362,350],[11,362],6590,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10380524",{"name":366,"url":367,"day":329},{"path":652,"title":653,"description":654,"date":655,"updated":9,"tags":656,"categories":657,"draft":15,"wordCount":658,"readingTime":318,"canonical":659,"series":660},"\u002Fithome-2025-day-01","Day 1：Hello World LINE Bot","每個程式語言的開始通常都會以練習 Hello World！作為一個簡單的起手式，LINE Bot 也不意外。這一系列會著重在 LINE Bot 的相關應用，帶大家用輕鬆的方式一起建立屬於自己的 Line Bot 聊天機器…","2025-09-14T22:48:33+08:00",[11,360,362,350],[11,362],2723,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10379895",{"name":366,"url":367,"day":340},{"path":662,"title":663,"description":664,"date":665,"updated":9,"tags":666,"categories":671,"draft":15,"wordCount":674,"readingTime":134,"canonical":9,"series":9},"\u002Fdbdiagram-tool","關聯式資料庫設計必備 - dbdiagram.io ERD 繪圖工具","還在煩惱如何設計關聯式資料表實體關聯圖嗎？ 使用 dbdiagram.io 加快你與同事之間的距離","2024-11-05T20:18:24+08:00",[667,668,669,670],"ERD","dbdiagram.io","DBML","資料庫設計",[672,673],"Database","開發工具",3982,{"path":676,"title":677,"description":678,"date":679,"updated":9,"tags":680,"categories":687,"draft":15,"wordCount":690,"readingTime":122,"canonical":9,"series":9},"\u002FPostgreSQL-1","前端勇士大闖六角後端體驗營 - 初探 PostgreSQL DML 語法","初步踏入後端的世界，了解何謂後端以及資料庫常見語法 DDL、DML。","2024-10-30T22:43:49+08:00",[681,682,683,684,685,686],"PostgreSQL","SQL","DML","DDL","資料庫","六角學院",[672,688,689],"後端實戰","學習心得",5220,{"path":692,"title":693,"description":694,"date":695,"updated":9,"tags":696,"categories":700,"draft":15,"wordCount":703,"readingTime":329,"canonical":9,"series":9},"\u002Fcss-flex-1","CSS flex 軸線對齊技巧 - 預設屬性","CSS flex 排版技巧你都了解了嗎？回歸學習初心的認識 flex 預設屬性","2024-10-24T20:29:08+08:00",[697,698,699],"Flexbox","前端排版","切版技巧",[701,702],"CSS","前端開發",1856,{"path":705,"title":706,"description":707,"date":708,"updated":9,"tags":709,"categories":712,"draft":15,"wordCount":714,"readingTime":329,"canonical":715,"series":716},"\u002Fithome-2024-day-30","Vue 3 用實作帶你看過核心概念 - Day 30：新手村的鐵人證書 - 完賽心得","花了整整半年左右的時間，每天下班都很用心的準備範例或是看書，這一段旅程對我來說真的很重要。擔心、害怕跟不安的心情一直充斥自己心中，擔心寫錯或是寫不好，想讓自己再多準備久一點再開始，所以拖到中秋連假才開始，想下定決心讓自己…","2024-10-13T12:42:25+08:00",[11,710,711],"2024 鐵人賽","Vue 3",[11,713],"Vue",1381,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10367206",{"name":717,"url":718,"day":22},"2024 iThome 鐵人賽：Vue 3 初學者：用實作帶你看過核心概念","https:\u002F\u002Fithelp.ithome.com.tw\u002Fusers\u002F20116554\u002Fironman\u002F7800",{"path":720,"title":721,"description":722,"date":723,"updated":9,"tags":724,"categories":727,"draft":15,"wordCount":728,"readingTime":134,"canonical":729,"series":730},"\u002Fithome-2024-day-29","Vue 3 用實作帶你看過核心概念 - Day 29：Composables(組合式函數)","Composables是 Vue 3 中的一個重要概念，它是利用 Vue 的 Composition API 來創建和管理可重用的有狀態邏輯的函數。Composables 允許我們將組件邏輯抽象成可重用的函數，使得代碼更…","2024-10-12T15:07:55+08:00",[11,710,711,725,726],"Options API","Composition API",[11,713],4233,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10366865",{"name":717,"url":718,"day":34},{"path":732,"title":733,"description":734,"date":735,"updated":9,"tags":736,"categories":738,"draft":15,"wordCount":739,"readingTime":134,"canonical":740,"series":741},"\u002Fithome-2024-day-28","Vue 3 用實作帶你看過核心概念 - Day 28：官方 Vue Router 的應用與技巧","路由設定方式，一般可以分成兩種：","2024-10-11T17:14:44+08:00",[11,710,711,725,737],"Vue Router",[11,713],4095,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10366520",{"name":717,"url":718,"day":45},{"path":743,"title":744,"description":745,"date":746,"updated":9,"tags":747,"categories":748,"draft":15,"wordCount":749,"readingTime":122,"canonical":750,"series":751},"\u002Fithome-2024-day-27","Vue 3 用實作帶你看過核心概念 - Day 27：VeeValidate 表單驗證工具","在開發 Vue 應用程式時，表單驗證至關重要，它能確保使用者輸入的資料符合預期的格式和要求。今天來介紹一款專門用於 Vue 的表單驗證工具—VeeValidate v4。這個工具不僅能幫助開發者輕鬆實現表單驗證，還提供了…","2024-10-10T21:02:16+08:00",[11,710,711,725],[11,713],4461,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10366165",{"name":717,"url":718,"day":56},{"path":753,"title":754,"description":755,"date":756,"updated":9,"tags":757,"categories":758,"draft":15,"wordCount":759,"readingTime":318,"canonical":760,"series":761},"\u002Fithome-2024-day-26","Vue 3 用實作帶你看過核心概念 - Day 26：KeepAlive 與 Teleport 內置組件","預設情況下，切換組件會導致組件的創建及銷毀，無法保持組件內部的狀態。","2024-10-09T18:50:53+08:00",[11,710,711,725],[11,713],3158,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10365724",{"name":717,"url":718,"day":67},{"path":763,"title":764,"description":765,"date":766,"updated":9,"tags":767,"categories":768,"draft":15,"wordCount":769,"readingTime":318,"canonical":770,"series":771},"\u002Fithome-2024-day-25","Vue 3 用實作帶你看過核心概念 - Day 25：自定義指令(Custom Directives)","Vue不僅提供了豐富的內置指令用於操作DOM元素，還允許開發者創建自定義指令。透過自定義指令，我們可以對DOM元素進行更細緻的控制，並在指令的不同生命週期階段執行特定的邏輯，從而實現更靈活的功能擴展。","2024-10-08T22:05:32+08:00",[11,710,711,725],[11,713],3116,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10365355",{"name":717,"url":718,"day":79},{"path":773,"title":774,"description":775,"date":776,"updated":9,"tags":777,"categories":778,"draft":15,"wordCount":779,"readingTime":329,"canonical":780,"series":781},"\u002Fithome-2024-day-24","Vue 3 用實作帶你看過核心概念 - Day 24：異步加載(Lazy loading)","在大型項目中，我們通常需要將應用拆分為更小的代碼模塊，並僅在需要時才從服務器加載相關組件，以減少初次加載的資源量。Vue 提供了 defineAsyncComponent 方法來實現組件的異步加載，這樣可以有效提高應用的…","2024-10-07T20:28:27+08:00",[11,710,711,725],[11,713],2171,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10364861",{"name":717,"url":718,"day":90},{"path":783,"title":784,"description":785,"date":786,"updated":9,"tags":787,"categories":788,"draft":15,"wordCount":789,"readingTime":318,"canonical":790,"series":791},"\u002Fithome-2024-day-23","Vue 3 用實作帶你看過核心概念 - Day 23：依賴注入( Provide-Inject )","在Dayxx中提到過，父子組件之間的數據傳遞可以通過props來實現。然而，當祖孫層組件需要進行數據傳遞時，中間層級的組件可能並不需要這個props，但卻不得不為了使子組件獲取數據而被迫傳遞。","2024-10-06T15:14:51+08:00",[11,710,711,725],[11,713],3183,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10364252",{"name":717,"url":718,"day":101},{"path":793,"title":794,"description":795,"date":796,"updated":9,"tags":797,"categories":798,"draft":15,"wordCount":799,"readingTime":134,"canonical":800,"series":801},"\u002Fithome-2024-day-22","Vue 3 用實作帶你看過核心概念 - Day 22：v-slot 插槽使用","先前Day 18中提到父組件可以通過props將參數資料傳遞給子組件進行渲染，這樣可以有效地將資料處理與畫面渲染的工作分開。然而，當我們需要更加客製化的呈現內容，由父組件來控制子組件的部分顯示時，可以使用v-slot 指…","2024-10-05T12:32:51+08:00",[11,710,711,725],[11,713],4292,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10363713",{"name":717,"url":718,"day":113},{"path":803,"title":804,"description":805,"date":806,"updated":9,"tags":807,"categories":808,"draft":15,"wordCount":809,"readingTime":329,"canonical":810,"series":811},"\u002Fithome-2024-day-21","Vue 3 用實作帶你看過核心概念 - Day 21： 組件透傳屬性(Attributes)的應用","當父組件向子組件傳遞屬性或監聽器，而這些屬性未在子組件中定義為props或emits時，它們會自動繼承並附加到子組件的根元素上，常見的屬性包括class、style、id等。這些屬性可以透過$attrs屬性訪問並進行操作…","2024-10-04T22:42:54+08:00",[11,710,711,725],[11,713],2146,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10363469",{"name":717,"url":718,"day":125},{"path":813,"title":814,"description":815,"date":816,"updated":9,"tags":817,"categories":818,"draft":15,"wordCount":819,"readingTime":122,"canonical":820,"series":821},"\u002Fithome-2024-day-20","Vue 3 用實作帶你看過核心概念 - Day 20：組件使用 v-model 雙向綁定","在Day 13中，我們介紹了表單元素（例如輸入框）如何處理數據雙向綁定。當需要將表單元素與一個響應式變數綁定時，v-model提供雙向綁定功能。這代表當畫面上表單元素的變動會即時影響Vue數據，同時當Vue數據的值改變時…","2024-10-03T23:40:48+08:00",[11,710,711,725],[11,713],5191,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10362957",{"name":717,"url":718,"day":137},{"path":823,"title":824,"description":825,"date":826,"updated":9,"tags":827,"categories":828,"draft":15,"wordCount":829,"readingTime":318,"canonical":830,"series":831},"\u002Fithome-2024-day-19","Vue 3 用實作帶你看過核心概念 - Day 19：子組件向父組件發射事件 - emits","組件可以通過$emit(functionName, params)自定義事件名稱並傳遞參數。自定義事件名稱建議使用kebab-case來命名自定義事件名稱，以確保命名風格與HTML 屬性一致性。此外，自定義事件還支持事件…","2024-10-02T15:51:16+08:00",[11,710,711,725],[11,713],2877,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10362144",{"name":717,"url":718,"day":148},{"path":833,"title":834,"description":835,"date":836,"updated":9,"tags":837,"categories":838,"draft":15,"wordCount":839,"readingTime":31,"canonical":840,"series":841},"\u002Fithome-2024-day-18","Vue 3 用實作帶你看過核心概念 - Day 18：父組件向子組件傳值 - props","組件之間的傳值必須透過聲明 props 的方式，這樣可以避免與HTML的屬性(attribute)混淆。父組件傳遞參數給子組件的方式有以下兩種方式：","2024-10-01T22:28:48+08:00",[11,710,711,725],[11,713],5782,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10361721",{"name":717,"url":718,"day":159},{"path":843,"title":844,"description":845,"date":846,"updated":9,"tags":847,"categories":848,"draft":15,"wordCount":849,"readingTime":318,"canonical":850,"series":851},"\u002Fithome-2024-day-17","Vue 3 用實作帶你看過核心概念 - Day 17：組件基礎 - 全局註冊與區域註冊","在 Day 5 我們提到可以將應用拆分為根組件和多個組件實例，並將功能模塊化。","2024-09-30T23:30:10+08:00",[11,710,711,725],[11,713],2954,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10361253",{"name":717,"url":718,"day":170},{"path":853,"title":854,"description":855,"date":856,"updated":9,"tags":857,"categories":858,"draft":15,"wordCount":859,"readingTime":122,"canonical":860,"series":861},"\u002Fithome-2024-day-16","Vue 3 用實作帶你看過核心概念 - Day 16：使用 ref 屬性引用模板元素","雖然 Vue 透過模板幫助我們處理了大部分的DOM 操作，但在某些情況下，仍然需要手動操作 DOM。例如，在使用第三方套件（如 Bootstrap 的 Modal）時，或者需要調用像video.play()和video.…","2024-09-29T16:25:45+08:00",[11,710,711,725],[11,713],4608,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10360419",{"name":717,"url":718,"day":181},{"path":863,"title":864,"description":865,"date":866,"updated":9,"tags":867,"categories":868,"draft":15,"wordCount":488,"readingTime":122,"canonical":869,"series":870},"\u002Fithome-2024-day-15","Vue 3 用實作帶你看過核心概念 - Day 15：監聽器(Watchers)","在響應式狀態變化時執行特定操作，需要使用watch 監聽器。這些操作可能包含副作用 (side effects)，如發送請求、調用 API，或更新 DOM 以外的系統狀態，亦或是處理自定義的邏輯。","2024-09-28T18:30:31+08:00",[11,710,711,725],[11,713],"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10359958",{"name":717,"url":718,"day":192},{"path":872,"title":873,"description":874,"date":875,"updated":9,"tags":876,"categories":877,"draft":15,"wordCount":878,"readingTime":134,"canonical":879,"series":880},"\u002Fithome-2024-day-14","Vue 3 用實作帶你看過核心概念 - Day 14：生命週期鉤子(Lifecycle Hooks)","在 Vue 中，當一個組件創建時，它會經歷一系列的流程，這些流程決定了組件從初始化到渲染，再到最終銷毀的過程。","2024-09-27T21:57:32+08:00",[11,710,711,725],[11,713],3780,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10359419",{"name":717,"url":718,"day":203},{"path":882,"title":883,"description":884,"date":885,"updated":9,"tags":886,"categories":887,"draft":15,"wordCount":888,"readingTime":318,"canonical":889,"series":890},"\u002Fithome-2024-day-13","Vue 3 用實作帶你看過核心概念 - Day 13：v-model 雙向綁定控制 - 常用於表單輸入","表單輸入是開發中非常常見的需求，原始方式通常需要手動使用v-bind綁定表單元素的值 (value)，並透過v-on綁定input事件來監聽輸入變化。","2024-09-26T21:49:25+08:00",[11,710,711,725],[11,713],3153,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10358850",{"name":717,"url":718,"day":214},{"path":892,"title":893,"description":894,"date":895,"updated":9,"tags":896,"categories":897,"draft":15,"wordCount":898,"readingTime":134,"canonical":899,"series":900},"\u002Fithome-2024-day-12","Vue 3 用實作帶你看過核心概念 - Day 12：v-on 指令 - 事件處理","在 Vue 中，監聽 HTML DOM 元素的事件變化，可以使用v-on 指令將 JavaScript 事件處理器綁定到對應的DOM 事件上。這個指令的縮寫符號為@，例如：v-on:click，其縮寫方式為@click。","2024-09-25T21:42:59+08:00",[11,710,711,725],[11,713],3808,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10358241",{"name":717,"url":718,"day":225},{"path":902,"title":903,"description":904,"date":905,"updated":9,"tags":906,"categories":907,"draft":15,"wordCount":908,"readingTime":134,"canonical":909,"series":910},"\u002Fithome-2024-day-11","Vue 3 用實作帶你看過核心概念 - Day 11：v-for 指令的基本與進階應用","當數據類型是陣列或物件時，我們不會希望將重複的選項或元素直接硬編碼在模板中。這樣的寫法會導致代碼冗長且不具可維護性。","2024-09-24T23:11:25+08:00",[11,710,711,725],[11,713],4381,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10357705",{"name":717,"url":718,"day":236},{"path":912,"title":913,"description":914,"date":915,"updated":9,"tags":916,"categories":917,"draft":15,"wordCount":918,"readingTime":318,"canonical":919,"series":920},"\u002Fithome-2024-day-10","Vue 3 用實作帶你看過核心概念 - Day 10：條件渲染 - v-if 與 v-show 的區別與應用","開發中遇到需要處理【如果是...就要...】的情境時，Vue 提供了 v-if 指令來幫助我們根據條件進行元素的渲染。這樣可以在特定條件滿足時動態地顯示或隱藏某些元素。","2024-09-23T23:39:24+08:00",[11,710,711,725],[11,713],2827,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10357160",{"name":717,"url":718,"day":247},{"path":922,"title":923,"description":924,"date":925,"updated":9,"tags":926,"categories":927,"draft":15,"wordCount":928,"readingTime":122,"canonical":929,"series":930},"\u002Fithome-2024-day-09","Vue 3 用實作帶你看過核心概念 - Day 9：Class 與 Style 的樣式綁定","v-bind 指令通常用於綁定 HTML 屬性，但在實際開發中，最常見的應用場景之一還是綁定class和 style這兩個 HTML 屬性。","2024-09-22T15:05:53+08:00",[11,710,711,725],[11,713],4630,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10356119",{"name":717,"url":718,"day":76},{"path":932,"title":933,"description":934,"date":935,"updated":9,"tags":936,"categories":937,"draft":15,"wordCount":938,"readingTime":134,"canonical":939,"series":940},"\u002Fithome-2024-day-08","Vue 3 用實作帶你看過核心概念 - Day 8：計算屬性應用與實踐","在 Day 6 在使用 Vue 的Mustache語法時，可以用來放置表達式。不過，這邊建議避免在 Mustache 中撰寫過於複雜的邏輯。這樣的建議主要有兩個原因：","2024-09-21T21:33:29+08:00",[11,710,711,725],[11,713],4060,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10355474",{"name":717,"url":718,"day":110},{"path":942,"title":943,"description":944,"date":945,"updated":9,"tags":946,"categories":947,"draft":15,"wordCount":948,"readingTime":122,"canonical":949,"series":950},"\u002Fithome-2024-day-07","Vue 3 用實作帶你看過核心概念 - Day 7： 響應式基礎 - Options API","前面提到了模板搭配指令及 Mustache 的用法。今天則是要介紹所謂透過getter跟setter取值跟賦值的方式在 JavaScript 是怎麼做到的，這部分會跟 Vue 本身的響應式基礎有很大相關。此外，我也會帶著…","2024-09-20T23:41:42+08:00",[11,710,711],[11,713],4407,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10354781",{"name":717,"url":718,"day":17},{"path":952,"title":953,"description":954,"date":955,"updated":9,"tags":956,"categories":957,"draft":15,"wordCount":958,"readingTime":134,"canonical":959,"series":960},"\u002Fithome-2024-day-06","Vue 3 用實作帶你看過核心概念 - Day 6：數據綁定和模板(Template)","Vue 使用基於 HTML 的模板語法（Template Syntax），將 Vue 實例中的數據綁定到模板中進行顯示。根據綁定的類型不同，數據可以影響元素的內容、屬性或樣式。","2024-09-19T20:39:46+08:00",[11,710,711,725],[11,713],3402,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10354231",{"name":717,"url":718,"day":31},{"path":962,"title":963,"description":964,"date":965,"updated":9,"tags":966,"categories":967,"draft":15,"wordCount":968,"readingTime":318,"canonical":969,"series":970},"\u002Fithome-2024-day-05","Vue 3 用實作帶你看過核心概念 - Day 5：Options API vs Composition API","前面已經詳細說明了 Vue 的環境建置方式，接下來將逐步介紹 Vue 應用說明，並比較Options API與Composition API的撰寫風格。","2024-09-18T22:10:36+08:00",[11,710,711,725,726],[11,713],2902,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10353717",{"name":717,"url":718,"day":122},{"path":972,"title":973,"description":974,"date":975,"updated":9,"tags":976,"categories":977,"draft":15,"wordCount":658,"readingTime":318,"canonical":978,"series":979},"\u002Fithome-2024-day-04","Vue 3 用實作帶你看過核心概念 - Day 4：基於 Vite 建構工具的 Vue 專案結構解析","在 Day 3 的教學中，學習了如何使用 Vue 鷹架工具(Vue project scaffolding tool)來生成一個基於 Vite 環境的專案。今天，我將更進一步地解析這個專案的資料夾結構，幫助大家更好地理解…","2024-09-17T23:27:07+08:00",[11,710,711,725],[11,713],"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10352470",{"name":717,"url":718,"day":134},{"path":981,"title":982,"description":983,"date":984,"updated":9,"tags":985,"categories":986,"draft":15,"wordCount":987,"readingTime":329,"canonical":988,"series":989},"\u002Fithome-2024-day-03","Vue 3 用實作帶你看過核心概念 - Day 3：使用 Vite 建構工具搭建 Vue 專案","⭐ 需要特別注意，需要事先安裝Nodejs。若不確定是否已安裝 Nodejs 也可以開啟終端機，執行以下指令確認版本。","2024-09-16T23:29:45+08:00",[11,710,711,725],[11,713],2100,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10351398",{"name":717,"url":718,"day":318},{"path":991,"title":992,"description":993,"date":994,"updated":9,"tags":995,"categories":996,"draft":15,"wordCount":997,"readingTime":329,"canonical":998,"series":999},"\u002Fithome-2024-day-02","Vue 3 用實作帶你看過核心概念 - Day 2：命令式編程 vs 聲明式編程","還記得我們在使用純 HTML 和 JavaScript 時，如何實現按下按鈕來變更文字顏色嗎？讓我們一起回想一下什麼是命令式宣告。","2024-09-15T22:52:27+08:00",[11,710,711,725,726],[11,713],1881,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10350459",{"name":717,"url":718,"day":329},{"path":1001,"title":1002,"description":1003,"date":1004,"updated":9,"tags":1005,"categories":1006,"draft":15,"wordCount":1007,"readingTime":340,"canonical":1008,"series":1009},"\u002Fithome-2024-day-01","Vue 3 用實作帶你看過核心概念 - Day 1：前言介紹","大家好，我是 Antonio(安東尼奧)，目前是一名 Angular 的新手工程師，同時，也是一名熱愛寫程式的工程師。去年參加六角學院的 Vue 直播班，在那時候就深深著迷著 Vue 的語言，自此這半年多的時間裡，我白天…","2024-09-14T17:32:19+08:00",[11,710,711],[11,713],988,"https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10350457",{"name":717,"url":718,"day":340},{"path":1011,"title":1012,"description":1013,"date":1014,"updated":9,"tags":1015,"categories":1017,"draft":15,"wordCount":1019,"readingTime":329,"canonical":9,"series":9},"\u002Fhexschool-experience","六角學院 - 軟體工程師體驗營","這麼多線上課程? 你不想知道什麼是最吸引我的線上學習課程嗎?","2024-05-31T21:40:05+08:00",[686,1016],"軟體工程師體驗營",[689,1018],"前端成長記",1109,{"path":1021,"title":1022,"description":1023,"date":1024,"updated":9,"tags":1025,"categories":1033,"draft":15,"wordCount":1035,"readingTime":318,"canonical":9,"series":9},"\u002FJS-10","JavaScript：var、let、const之間的差異解析","var、let及const是JavaScript中宣告變數的關鍵字，它們在作用域、對於for迴圈的影響、提升 (Hoisting) 的差異及重複宣告的處理上有所不同。","2023-12-10T23:53:30+08:00",[1026,1027,1028,1029,1030,1031,1032],"ES6","var","let","const","Hoisting","Scope","TDZ",[1034,702],"JavaScript",2873,{"path":1037,"title":1038,"description":1039,"date":1040,"updated":9,"tags":1041,"categories":1048,"draft":15,"wordCount":1049,"readingTime":122,"canonical":9,"series":9},"\u002FJS-9","JavaScript：陣列的常見操作方法","掌握 JavaScript 陣列操作！從forEach、map、filter、findIndex、reduce、sort，深入了解這些方法，使你的程式碼更具靈活性和效率！","2023-12-01T15:01:06+08:00",[1042,1043,1044,1045,1046,1047],"forEach","map","filter","reduce","sort","findIndex",[1034,702],4846,{"path":1051,"title":1052,"description":1053,"date":1054,"updated":9,"tags":1055,"categories":1060,"draft":15,"wordCount":1061,"readingTime":318,"canonical":9,"series":9},"\u002FJS-8","JavaScript：ES module 匯入與匯出","使用\u003C script >標籤讓瀏覽器中的網頁能夠使用 ES 模組功能時，你了解不同的匯入及匯出方法了嗎？","2023-11-30T15:28:23+08:00",[1026,1056,1057,1058,1059],"ES Module","CommonJS","Node.js","模組化開發",[1034,702],2913,{"path":1063,"title":1064,"description":1065,"date":1066,"updated":9,"tags":1067,"categories":1070,"draft":15,"wordCount":1072,"readingTime":134,"canonical":9,"series":9},"\u002FReact-2","React：JSX 是什麼？為何在 React 中使用 JSX？","透過 JSX 語法糖，你能夠在一次 JavaScript 撰寫中包含 HTML 與 CSS 的內容","2023-11-29T16:03:40+08:00",[1068,1034,1069],"JSX","Babel",[1071,702],"React",3595,{"path":1074,"title":1075,"description":1076,"date":1077,"updated":9,"tags":1078,"categories":1081,"draft":15,"wordCount":1082,"readingTime":329,"canonical":9,"series":9},"\u002FReact-1","React：Pure Function 的概念及其在 React 中的應用","探索 Pure Function 概念及其與 React 的相互關係。","2023-11-29T15:50:17+08:00",[1034,1079,1080],"Pure Function","Functional Programming",[1071],1368,{"path":1084,"title":1085,"description":1086,"date":1087,"updated":9,"tags":1088,"categories":1093,"draft":15,"wordCount":1094,"readingTime":122,"canonical":9,"series":9},"\u002FJS-6","JavaScript：深入理解Promise的原理及實際應用","透過Promise，我們能夠有效解決JavaScript中的異步事件，使得程式碼更為清晰和可讀。","2023-11-29T14:49:18+08:00",[1089,1090,1091,1092],"Promise","AsyncAwait","Asynchronous Programming","異步處理",[1034,702],5479,{"path":1096,"title":1097,"description":1098,"date":1099,"updated":9,"tags":1100,"categories":1105,"draft":15,"wordCount":1106,"readingTime":122,"canonical":9,"series":9},"\u002FJS-5","JavaScript：物件傳參考特性(Pass by Reference)","淺拷貝(shallow copy)與深拷貝(deep copy)在JavaScript中的應用與理解","2023-11-29T09:55:50+08:00",[1101,1102,1103,1104],"Pass by Reference","Shallow Copy","Deep Copy","傳值與傳參考",[1034,702],4544,{"path":1108,"title":1109,"description":1110,"date":1111,"updated":9,"tags":1112,"categories":1118,"draft":15,"wordCount":1120,"readingTime":329,"canonical":9,"series":9},"\u002FJS-4","JavaScript：注重關注點的分離","透過關注點分離的設計方式，可顯著提高系統整體的可維護性。","2023-11-29T09:44:29+08:00",[1113,1114,1115,1116,1117],"SoC","MVC","Design Patterns","Clean Code","Refactoring",[1034,1119],"Software Engineering",1977,{"path":1122,"title":1123,"description":1124,"date":1125,"updated":9,"tags":1126,"categories":1132,"draft":15,"wordCount":1133,"readingTime":329,"canonical":9,"series":9},"\u002FJS-3","JavaScript：掌握箭頭函式，優雅簡約的程式寫作","深入解析ES6箭頭函數：揭開與傳統函式的差異。","2023-11-29T01:02:36+08:00",[1127,1128,1129,1130,1131],"Arrow Function","this","Lexical Scoping","Syntactic Sugar","Callback Function",[1034,702],2142,{"path":1135,"title":1136,"description":1137,"date":1138,"updated":9,"tags":1139,"categories":1143,"draft":15,"wordCount":1144,"readingTime":329,"canonical":9,"series":9},"\u002FJS-2","JavaScript：深入了解This的指向與應用","探索JavaScript This 的指向：深入剖析判斷的技巧。","2023-11-28T22:22:45+08:00",[1128,1140,1131,1141,1142],"Execution Context","Binding","Fundamentals",[1034,702],1963,{"path":1146,"title":1147,"description":1148,"date":1149,"updated":9,"tags":1150,"categories":1154,"draft":15,"wordCount":1155,"readingTime":340,"canonical":9,"series":9},"\u002FJS-1","JavaScript：淺談ES6常見縮寫","JavaScript ES6縮寫技巧：輕鬆簡化程式碼，提升效率！。","2023-11-28T13:38:14+08:00",[1026,1151,1152,1153],"Shorthand","Spread Operator","Object",[1034,702],818,{"path":1157,"title":1158,"description":1159,"date":1160,"updated":9,"tags":1161,"categories":1165,"draft":15,"wordCount":1166,"readingTime":340,"canonical":9,"series":9},"\u002FEmmet-1","前端開發神器：使用Emmet提升網頁開發效率","本文將介紹如何利用Emmet來大幅提高前端開發效率，讓你的編碼速度如專家般飛快。","2023-10-31T14:40:54+08:00",[1162,1163,1164],"Emmet","VSCode","HTML",[702,673],865,{"path":1168,"title":1169,"description":1170,"date":1171,"updated":9,"tags":1172,"categories":1177,"draft":15,"wordCount":1179,"readingTime":340,"canonical":9,"series":9},"\u002Fhexo-github-4","建立自己的部落格：了解Hexo指令，更換next部落格主題","今天的你跟我都是Hexo指令指揮官，讓我們將部落格造型大改造一下吧!!!","2023-10-20T14:33:38+08:00",[1173,1174,1175,1176],"Hexo","NexT","git clone","Hexo指令",[1178],"Blog",979,{"path":1181,"title":1182,"description":1183,"date":1184,"updated":9,"tags":1185,"categories":1189,"draft":15,"wordCount":1190,"readingTime":329,"canonical":9,"series":9},"\u002Fhexo-github-3","建立自己的部落格：修改Hexo設定檔中Site，查看其變化","今天的你跟我都是部落格工程師，我們一起來將網頁做一些小小的改變，有一天匯集在一起就會讓你的部落格變的與眾不同。","2023-10-18T20:50:31+08:00",[1173,1163,1186,1187,1188],"Sublime Text","YAML","SEO",[1178],1798,{"path":1192,"title":1193,"description":1194,"date":1195,"updated":9,"tags":1196,"categories":1199,"draft":15,"wordCount":1200,"readingTime":340,"canonical":9,"series":9},"\u002Fhexo-github-2","建立自己的部落格：創建Hexo Blog初始的模板","踏出那關鍵的第一步，從白紙到完整的部落格，一同見證你的部落格誕生的奇妙時刻。","2023-10-17T15:59:37+08:00",[1173,1197,1198],"Hexo-cli","hexo init",[1178],941,{"path":1202,"title":1203,"description":1204,"date":1205,"updated":9,"tags":1206,"categories":1208,"draft":15,"wordCount":1209,"readingTime":340,"canonical":9,"series":9},"\u002Fhexo-github-1","建立自己的部落格：認識Hexo及所其需工具(Nodejs & Git)","打造屬於自己風格的部落格其實不難，讓我們一步步手把手教你如何實現這個目標。","2023-10-16T08:30:27+08:00",[1173,1058,1207],"Git",[1178],790,{"id":1211,"title":206,"body":1212,"canonical":212,"categories":3853,"date":208,"description":207,"draft":15,"extension":3854,"meta":3855,"navigation":1381,"ogImage":9,"path":205,"rawbody":3856,"readingTime":110,"robots":9,"schemaOrg":9,"seo":3857,"series":3858,"sitemap":3859,"slug":3866,"stem":3867,"tags":3868,"updated":9,"wordCount":211,"__hash__":3869},"posts\u002F2026-09-27-ithome-2026-day-13.md",{"type":1213,"value":1214,"toc":3837},"minimark",[1215,1235,1246,1249,1253,1256,1259,1262,1269,1275,1278,1303,1306,1317,1467,1470,1475,1479,1485,1771,1776,1779,1793,1799,1803,1808,1834,1844,1847,1882,1889,1895,1941,1951,2057,2062,2086,2096,2102,2111,2116,2122,2345,2348,2482,2485,2488,2589,2592,2675,2685,2693,2701,2715,2718,2752,2758,2770,2782,2819,2823,2829,2868,2871,2935,2938,2942,2948,2958,2976,2979,3014,3017,3040,3046,3061,3071,3077,3081,3087,3093,3192,3197,3213,3216,3265,3286,3289,3349,3352,3430,3433,3494,3497,3518,3524,3534,3538,3545,3600,3608,3613,3619,3666,3680,3686,3694,3698,3701,3730,3736,3742,3745,3748,3759,3764,3767,3833],[1216,1217,1218,1219,1223,1224,1223,1227,1230,1231,1234],"p",{},"路由狀態管理在前端開發中比較少被單獨拿出來討論，但在 Angular 裡，一次路由導航其實會經過不少階段，也會在過程中發出不同的 Router Event，例如 ",[1220,1221,1222],"code",{},"NavigationStart","、",[1220,1225,1226],{},"NavigationEnd",[1220,1228,1229],{},"NavigationCancel"," 與 ",[1220,1232,1233],{},"NavigationError","。",[1216,1236,1237,1238,1241,1242,1245],{},"過去要觀察這些狀態，通常會透過 ",[1220,1239,1240],{},"Router.events"," 取得事件，再搭配 ",[1220,1243,1244],{},"Router.getCurrentNavigation()"," 讀取正在執行的 Navigation。",[1216,1247,1248],{},"隨著 Angular 持續導入 Signal，Router 也開始提供對應的 Signal API。這篇就從導航事件開始，看看過去怎麼處理 Router State，以及現在有哪些狀態可以直接透過 Signal 取得。",[1250,1251,1252],"h2",{"id":1252},"路由狀態介紹",[1216,1254,1255],{},"在 Angular 中，路由導航不只是把一個網址換成另一個網址。",[1216,1257,1258],{},"從使用者點擊連結開始，到新的頁面完成導航，中間可能會經過路由比對、Guard 檢查、Resolver 執行、Lazy Loading 與元件啟用等階段。",[1216,1260,1261],{},"Angular Router 會在這些階段發出對應的事件，讓我們知道這次 Navigation 執行到哪個位置。",[1216,1263,1264],{},[1265,1266],"img",{"alt":1267,"src":1268},"Angular Router 導航事件流程","https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509053\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Frouter-navigation-flow-expanded.png",[1216,1270,1271,1272,1274],{},"一次導航通常會從 ",[1220,1273,1222],{}," 開始，接著進行路由比對。如果有設定 Guard 或 Resolver，也會進入對應的檢查與資料解析流程。",[1216,1276,1277],{},"最後會進入其中一種結果：",[1279,1280,1281,1287,1292,1297],"ul",{},[1282,1283,1284,1286],"li",{},[1220,1285,1226],{},"：導航成功完成",[1282,1288,1289,1291],{},[1220,1290,1229],{},"：導航被取消",[1282,1293,1294,1296],{},[1220,1295,1233],{},"：導航過程發生錯誤",[1282,1298,1299,1302],{},[1220,1300,1301],{},"NavigationSkipped","：Router 判斷這次導航不需要重新執行，例如再次導航到相同 URL",[1216,1304,1305],{},"實際流程不一定每次都會經過所有階段，例如沒有設定 Guard 或 Resolver 時，就不會進入對應的處理流程。",[1216,1307,1308,1309,1312,1313,1316],{},"如果想直接觀察 Router 發出的事件，可以在 ",[1220,1310,1311],{},"provideRouter()"," 加上 ",[1220,1314,1315],{},"withDebugTracing()","：",[1318,1319,1324],"pre",{"className":1320,"code":1321,"language":1322,"meta":1323,"style":1323},"language-ts shiki shiki-themes github-light github-dark-dimmed","import { ApplicationConfig } from '@angular\u002Fcore';\nimport {\n  provideRouter,\n  withDebugTracing,\n} from '@angular\u002Frouter';\n\nimport { routes } from '.\u002Fapp.routes';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter(\n      routes,\n      withDebugTracing()\n    ),\n  ],\n};\n","ts","",[1220,1325,1326,1348,1355,1360,1365,1377,1383,1397,1401,1425,1430,1439,1444,1452,1457,1462],{"__ignoreMap":1323},[1327,1328,1330,1334,1338,1341,1345],"span",{"class":1329,"line":340},"line",[1327,1331,1333],{"class":1332},"s7YZ4","import",[1327,1335,1337],{"class":1336},"sYgZi"," { ApplicationConfig } ",[1327,1339,1340],{"class":1332},"from",[1327,1342,1344],{"class":1343},"s-HuK"," '@angular\u002Fcore'",[1327,1346,1347],{"class":1336},";\n",[1327,1349,1350,1352],{"class":1329,"line":329},[1327,1351,1333],{"class":1332},[1327,1353,1354],{"class":1336}," {\n",[1327,1356,1357],{"class":1329,"line":318},[1327,1358,1359],{"class":1336},"  provideRouter,\n",[1327,1361,1362],{"class":1329,"line":134},[1327,1363,1364],{"class":1336},"  withDebugTracing,\n",[1327,1366,1367,1370,1372,1375],{"class":1329,"line":122},[1327,1368,1369],{"class":1336},"} ",[1327,1371,1340],{"class":1332},[1327,1373,1374],{"class":1343}," '@angular\u002Frouter'",[1327,1376,1347],{"class":1336},[1327,1378,1379],{"class":1329,"line":31},[1327,1380,1382],{"emptyLinePlaceholder":1381},true,"\n",[1327,1384,1385,1387,1390,1392,1395],{"class":1329,"line":17},[1327,1386,1333],{"class":1332},[1327,1388,1389],{"class":1336}," { routes } ",[1327,1391,1340],{"class":1332},[1327,1393,1394],{"class":1343}," '.\u002Fapp.routes'",[1327,1396,1347],{"class":1336},[1327,1398,1399],{"class":1329,"line":110},[1327,1400,1382],{"emptyLinePlaceholder":1381},[1327,1402,1403,1406,1409,1413,1416,1420,1423],{"class":1329,"line":76},[1327,1404,1405],{"class":1332},"export",[1327,1407,1408],{"class":1332}," const",[1327,1410,1412],{"class":1411},"sviXB"," appConfig",[1327,1414,1415],{"class":1332},":",[1327,1417,1419],{"class":1418},"sOLd2"," ApplicationConfig",[1327,1421,1422],{"class":1332}," =",[1327,1424,1354],{"class":1336},[1327,1426,1427],{"class":1329,"line":247},[1327,1428,1429],{"class":1336},"  providers: [\n",[1327,1431,1432,1436],{"class":1329,"line":236},[1327,1433,1435],{"class":1434},"sPO5f","    provideRouter",[1327,1437,1438],{"class":1336},"(\n",[1327,1440,1441],{"class":1329,"line":225},[1327,1442,1443],{"class":1336},"      routes,\n",[1327,1445,1446,1449],{"class":1329,"line":214},[1327,1447,1448],{"class":1434},"      withDebugTracing",[1327,1450,1451],{"class":1336},"()\n",[1327,1453,1454],{"class":1329,"line":203},[1327,1455,1456],{"class":1336},"    ),\n",[1327,1458,1459],{"class":1329,"line":192},[1327,1460,1461],{"class":1336},"  ],\n",[1327,1463,1464],{"class":1329,"line":181},[1327,1465,1466],{"class":1336},"};\n",[1216,1468,1469],{},"啟用後，導航期間的 Router Event 都會輸出到瀏覽器 Console，方便查看實際執行順序。",[1216,1471,1472,1474],{},[1220,1473,1315],{}," 主要用於開發與除錯，正式環境通常不需要開啟。",[1250,1476,1478],{"id":1477},"透過-routerevents-監聽路由事件","透過 Router.events 監聽路由事件",[1216,1480,1481,1482,1484],{},"Angular Router 提供 ",[1220,1483,1240],{},"，可以取得 Navigation 過程中發出的事件。",[1318,1486,1488],{"className":1320,"code":1487,"language":1322,"meta":1323,"style":1323},"import { inject } from '@angular\u002Fcore';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular\u002Frouter';\n\nexport class AppComponent {\n  private readonly router = inject(Router);\n\n  constructor() {\n    this.router.events.subscribe(event => {\n      if (event instanceof NavigationStart) {\n        console.log('開始導航');\n      }\n\n      if (event instanceof NavigationEnd) {\n        console.log('導航完成');\n      }\n\n      if (event instanceof NavigationCancel) {\n        console.log('導航取消');\n      }\n\n      if (event instanceof NavigationError) {\n        console.log('導航發生錯誤');\n      }\n    });\n  }\n}\n",[1220,1489,1490,1503,1509,1514,1519,1524,1529,1534,1544,1548,1560,1580,1584,1592,1614,1631,1647,1652,1656,1669,1682,1686,1690,1703,1716,1720,1724,1737,1750,1754,1759,1765],{"__ignoreMap":1323},[1327,1491,1492,1494,1497,1499,1501],{"class":1329,"line":340},[1327,1493,1333],{"class":1332},[1327,1495,1496],{"class":1336}," { inject } ",[1327,1498,1340],{"class":1332},[1327,1500,1344],{"class":1343},[1327,1502,1347],{"class":1336},[1327,1504,1505,1507],{"class":1329,"line":329},[1327,1506,1333],{"class":1332},[1327,1508,1354],{"class":1336},[1327,1510,1511],{"class":1329,"line":318},[1327,1512,1513],{"class":1336},"  NavigationCancel,\n",[1327,1515,1516],{"class":1329,"line":134},[1327,1517,1518],{"class":1336},"  NavigationEnd,\n",[1327,1520,1521],{"class":1329,"line":122},[1327,1522,1523],{"class":1336},"  NavigationError,\n",[1327,1525,1526],{"class":1329,"line":31},[1327,1527,1528],{"class":1336},"  NavigationStart,\n",[1327,1530,1531],{"class":1329,"line":17},[1327,1532,1533],{"class":1336},"  Router,\n",[1327,1535,1536,1538,1540,1542],{"class":1329,"line":110},[1327,1537,1369],{"class":1336},[1327,1539,1340],{"class":1332},[1327,1541,1374],{"class":1343},[1327,1543,1347],{"class":1336},[1327,1545,1546],{"class":1329,"line":76},[1327,1547,1382],{"emptyLinePlaceholder":1381},[1327,1549,1550,1552,1555,1558],{"class":1329,"line":247},[1327,1551,1405],{"class":1332},[1327,1553,1554],{"class":1332}," class",[1327,1556,1557],{"class":1418}," AppComponent",[1327,1559,1354],{"class":1336},[1327,1561,1562,1565,1568,1572,1574,1577],{"class":1329,"line":236},[1327,1563,1564],{"class":1332},"  private",[1327,1566,1567],{"class":1332}," readonly",[1327,1569,1571],{"class":1570},"stnAF"," router",[1327,1573,1422],{"class":1332},[1327,1575,1576],{"class":1434}," inject",[1327,1578,1579],{"class":1336},"(Router);\n",[1327,1581,1582],{"class":1329,"line":225},[1327,1583,1382],{"emptyLinePlaceholder":1381},[1327,1585,1586,1589],{"class":1329,"line":214},[1327,1587,1588],{"class":1332},"  constructor",[1327,1590,1591],{"class":1336},"() {\n",[1327,1593,1594,1597,1600,1603,1606,1609,1612],{"class":1329,"line":203},[1327,1595,1596],{"class":1411},"    this",[1327,1598,1599],{"class":1336},".router.events.",[1327,1601,1602],{"class":1434},"subscribe",[1327,1604,1605],{"class":1336},"(",[1327,1607,1608],{"class":1570},"event",[1327,1610,1611],{"class":1332}," =>",[1327,1613,1354],{"class":1336},[1327,1615,1616,1619,1622,1625,1628],{"class":1329,"line":192},[1327,1617,1618],{"class":1332},"      if",[1327,1620,1621],{"class":1336}," (event ",[1327,1623,1624],{"class":1332},"instanceof",[1327,1626,1627],{"class":1418}," NavigationStart",[1327,1629,1630],{"class":1336},") {\n",[1327,1632,1633,1636,1639,1641,1644],{"class":1329,"line":181},[1327,1634,1635],{"class":1336},"        console.",[1327,1637,1638],{"class":1434},"log",[1327,1640,1605],{"class":1336},[1327,1642,1643],{"class":1343},"'開始導航'",[1327,1645,1646],{"class":1336},");\n",[1327,1648,1649],{"class":1329,"line":170},[1327,1650,1651],{"class":1336},"      }\n",[1327,1653,1654],{"class":1329,"line":159},[1327,1655,1382],{"emptyLinePlaceholder":1381},[1327,1657,1658,1660,1662,1664,1667],{"class":1329,"line":148},[1327,1659,1618],{"class":1332},[1327,1661,1621],{"class":1336},[1327,1663,1624],{"class":1332},[1327,1665,1666],{"class":1418}," NavigationEnd",[1327,1668,1630],{"class":1336},[1327,1670,1671,1673,1675,1677,1680],{"class":1329,"line":137},[1327,1672,1635],{"class":1336},[1327,1674,1638],{"class":1434},[1327,1676,1605],{"class":1336},[1327,1678,1679],{"class":1343},"'導航完成'",[1327,1681,1646],{"class":1336},[1327,1683,1684],{"class":1329,"line":125},[1327,1685,1651],{"class":1336},[1327,1687,1688],{"class":1329,"line":113},[1327,1689,1382],{"emptyLinePlaceholder":1381},[1327,1691,1692,1694,1696,1698,1701],{"class":1329,"line":101},[1327,1693,1618],{"class":1332},[1327,1695,1621],{"class":1336},[1327,1697,1624],{"class":1332},[1327,1699,1700],{"class":1418}," NavigationCancel",[1327,1702,1630],{"class":1336},[1327,1704,1705,1707,1709,1711,1714],{"class":1329,"line":90},[1327,1706,1635],{"class":1336},[1327,1708,1638],{"class":1434},[1327,1710,1605],{"class":1336},[1327,1712,1713],{"class":1343},"'導航取消'",[1327,1715,1646],{"class":1336},[1327,1717,1718],{"class":1329,"line":79},[1327,1719,1651],{"class":1336},[1327,1721,1722],{"class":1329,"line":67},[1327,1723,1382],{"emptyLinePlaceholder":1381},[1327,1725,1726,1728,1730,1732,1735],{"class":1329,"line":56},[1327,1727,1618],{"class":1332},[1327,1729,1621],{"class":1336},[1327,1731,1624],{"class":1332},[1327,1733,1734],{"class":1418}," NavigationError",[1327,1736,1630],{"class":1336},[1327,1738,1739,1741,1743,1745,1748],{"class":1329,"line":45},[1327,1740,1635],{"class":1336},[1327,1742,1638],{"class":1434},[1327,1744,1605],{"class":1336},[1327,1746,1747],{"class":1343},"'導航發生錯誤'",[1327,1749,1646],{"class":1336},[1327,1751,1752],{"class":1329,"line":34},[1327,1753,1651],{"class":1336},[1327,1755,1756],{"class":1329,"line":22},[1327,1757,1758],{"class":1336},"    });\n",[1327,1760,1762],{"class":1329,"line":1761},31,[1327,1763,1764],{"class":1336},"  }\n",[1327,1766,1768],{"class":1329,"line":1767},32,[1327,1769,1770],{"class":1336},"}\n",[1216,1772,1773,1775],{},[1220,1774,1240],{}," 是一個 Observable，會隨著 Navigation 進行持續發出不同類型的 Router Event。",[1216,1777,1778],{},"它適合用在需要針對特定事件處理的情境，例如頁面瀏覽紀錄、錯誤追蹤，或觀察 Guard、Resolver 的執行狀態。",[1216,1780,1781,1782,1784,1785,1223,1787,1789,1790,1792],{},"如果要顯示頁面切換中的 Loading，也可以在 ",[1220,1783,1222],{}," 時開啟，再於 ",[1220,1786,1226],{},[1220,1788,1229],{}," 或 ",[1220,1791,1233],{}," 時關閉。",[1216,1794,1795,1796,1798],{},"不過，如果只是想知道「目前是否有 Navigation 正在進行」，就不一定要自己從多個事件整理出狀態。過去可以透過 ",[1220,1797,1244],{}," 直接讀取目前正在執行的 Navigation。",[1250,1800,1802],{"id":1801},"從-getcurrentnavigation-到-currentnavigation-signal","從 getCurrentNavigation() 到 currentNavigation Signal",[1216,1804,1805,1807],{},[1220,1806,1244],{}," 會回傳目前正在執行的 Navigation：",[1318,1809,1811],{"className":1320,"code":1810,"language":1322,"meta":1323,"style":1323},"const navigation = this.router.getCurrentNavigation();\n",[1220,1812,1813],{"__ignoreMap":1323},[1327,1814,1815,1817,1820,1822,1825,1828,1831],{"class":1329,"line":340},[1327,1816,1029],{"class":1332},[1327,1818,1819],{"class":1411}," navigation",[1327,1821,1422],{"class":1332},[1327,1823,1824],{"class":1411}," this",[1327,1826,1827],{"class":1336},".router.",[1327,1829,1830],{"class":1434},"getCurrentNavigation",[1327,1832,1833],{"class":1336},"();\n",[1216,1835,1836,1837,1840,1841,1234],{},"Navigation 進行期間會取得對應的 ",[1220,1838,1839],{},"Navigation"," 物件，沒有導航正在執行時則會回傳 ",[1220,1842,1843],{},"null",[1216,1845,1846],{},"例如可以用來判斷路由是否正在切換：",[1318,1848,1850],{"className":1320,"code":1849,"language":1322,"meta":1323,"style":1323},"const isNavigating =\n  this.router.getCurrentNavigation() !== null;\n",[1220,1851,1852,1862],{"__ignoreMap":1323},[1327,1853,1854,1856,1859],{"class":1329,"line":340},[1327,1855,1029],{"class":1332},[1327,1857,1858],{"class":1411}," isNavigating",[1327,1860,1861],{"class":1332}," =\n",[1327,1863,1864,1867,1869,1871,1874,1877,1880],{"class":1329,"line":329},[1327,1865,1866],{"class":1411},"  this",[1327,1868,1827],{"class":1336},[1327,1870,1830],{"class":1434},[1327,1872,1873],{"class":1336},"() ",[1327,1875,1876],{"class":1332},"!==",[1327,1878,1879],{"class":1411}," null",[1327,1881,1347],{"class":1336},[1216,1883,1884,1885,1888],{},"不過 ",[1220,1886,1887],{},"getCurrentNavigation()"," 只會讀取執行當下的值，本身不具備響應式能力。",[1216,1890,1891,1892,1894],{},"當 Router 之後開始或完成另一個 Navigation，前面讀到的結果不會自動更新，因此過去常會搭配 ",[1220,1893,1240],{},"，在事件發生時重新讀取：",[1318,1896,1898],{"className":1320,"code":1897,"language":1322,"meta":1323,"style":1323},"this.router.events.pipe(\n  map(() => this.router.getCurrentNavigation() !== null)\n);\n",[1220,1899,1900,1911,1937],{"__ignoreMap":1323},[1327,1901,1902,1904,1906,1909],{"class":1329,"line":340},[1327,1903,1128],{"class":1411},[1327,1905,1599],{"class":1336},[1327,1907,1908],{"class":1434},"pipe",[1327,1910,1438],{"class":1336},[1327,1912,1913,1916,1919,1922,1924,1926,1928,1930,1932,1934],{"class":1329,"line":329},[1327,1914,1915],{"class":1434},"  map",[1327,1917,1918],{"class":1336},"(() ",[1327,1920,1921],{"class":1332},"=>",[1327,1923,1824],{"class":1411},[1327,1925,1827],{"class":1336},[1327,1927,1830],{"class":1434},[1327,1929,1873],{"class":1336},[1327,1931,1876],{"class":1332},[1327,1933,1879],{"class":1411},[1327,1935,1936],{"class":1336},")\n",[1327,1938,1939],{"class":1329,"line":318},[1327,1940,1646],{"class":1336},[1216,1942,1943,1944,1946,1947,1950],{},"到了 Angular 20.2，",[1220,1945,1887],{}," 已經標記為 Deprecated，改由 ",[1220,1948,1949],{},"currentNavigation"," Signal 取代。",[1318,1952,1954],{"className":1320,"code":1953,"language":1322,"meta":1323,"style":1323},"import { computed, inject } from '@angular\u002Fcore';\nimport { Router } from '@angular\u002Frouter';\n\nexport class AppComponent {\n  private readonly router = inject(Router);\n\n  readonly isNavigating = computed(\n    () => this.router.currentNavigation() !== null\n  );\n}\n",[1220,1955,1956,1969,1982,1986,1996,2010,2014,2028,2048,2053],{"__ignoreMap":1323},[1327,1957,1958,1960,1963,1965,1967],{"class":1329,"line":340},[1327,1959,1333],{"class":1332},[1327,1961,1962],{"class":1336}," { computed, inject } ",[1327,1964,1340],{"class":1332},[1327,1966,1344],{"class":1343},[1327,1968,1347],{"class":1336},[1327,1970,1971,1973,1976,1978,1980],{"class":1329,"line":329},[1327,1972,1333],{"class":1332},[1327,1974,1975],{"class":1336}," { Router } ",[1327,1977,1340],{"class":1332},[1327,1979,1374],{"class":1343},[1327,1981,1347],{"class":1336},[1327,1983,1984],{"class":1329,"line":318},[1327,1985,1382],{"emptyLinePlaceholder":1381},[1327,1987,1988,1990,1992,1994],{"class":1329,"line":134},[1327,1989,1405],{"class":1332},[1327,1991,1554],{"class":1332},[1327,1993,1557],{"class":1418},[1327,1995,1354],{"class":1336},[1327,1997,1998,2000,2002,2004,2006,2008],{"class":1329,"line":122},[1327,1999,1564],{"class":1332},[1327,2001,1567],{"class":1332},[1327,2003,1571],{"class":1570},[1327,2005,1422],{"class":1332},[1327,2007,1576],{"class":1434},[1327,2009,1579],{"class":1336},[1327,2011,2012],{"class":1329,"line":31},[1327,2013,1382],{"emptyLinePlaceholder":1381},[1327,2015,2016,2019,2021,2023,2026],{"class":1329,"line":17},[1327,2017,2018],{"class":1332},"  readonly",[1327,2020,1858],{"class":1570},[1327,2022,1422],{"class":1332},[1327,2024,2025],{"class":1434}," computed",[1327,2027,1438],{"class":1336},[1327,2029,2030,2033,2035,2037,2039,2041,2043,2045],{"class":1329,"line":110},[1327,2031,2032],{"class":1336},"    () ",[1327,2034,1921],{"class":1332},[1327,2036,1824],{"class":1411},[1327,2038,1827],{"class":1336},[1327,2040,1949],{"class":1434},[1327,2042,1873],{"class":1336},[1327,2044,1876],{"class":1332},[1327,2046,2047],{"class":1411}," null\n",[1327,2049,2050],{"class":1329,"line":76},[1327,2051,2052],{"class":1336},"  );\n",[1327,2054,2055],{"class":1329,"line":247},[1327,2056,1770],{"class":1336},[1216,2058,2059,2061],{},[1220,2060,1949],{}," 的型別是：",[1318,2063,2065],{"className":1320,"code":2064,"language":1322,"meta":1323,"style":1323},"Signal\u003CNavigation | null>\n",[1220,2066,2067],{"__ignoreMap":1323},[1327,2068,2069,2072,2075,2078,2081,2083],{"class":1329,"line":340},[1327,2070,2071],{"class":1336},"Signal",[1327,2073,2074],{"class":1332},"\u003C",[1327,2076,2077],{"class":1336},"Navigation ",[1327,2079,2080],{"class":1332},"|",[1327,2082,1879],{"class":1411},[1327,2084,2085],{"class":1332},">\n",[1216,2087,2088,2089,2091,2092,2095],{},"Navigation 開始後會取得這次 Navigation 的資訊，結束後則回到 ",[1220,2090,1843],{},"。依賴它的 ",[1220,2093,2094],{},"computed()"," 也會跟著重新計算，不需要再透過 Router Event 觸發讀取。",[1216,2097,2098],{},[1265,2099],{"alt":2100,"src":2101},"getCurrentNavigation() 與 currentNavigation 的差異","https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509050\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Frouter-method-vs-signal.png",[1216,2103,2104,2107,2108,1234],{},[1220,2105,2106],{},"currentNavigation()"," 代表的是正在執行的 Navigation。如果導航已經完成，要讀取目前所在的 URL，則可以使用 ",[1220,2109,2110],{},"router.url",[2112,2113,2115],"h3",{"id":2114},"實際觀察-currentnavigation","實際觀察 currentNavigation()",[1216,2117,2118,2119,2121],{},"可以直接把 ",[1220,2120,2106],{}," 的內容顯示在 Template 中：",[1318,2123,2127],{"className":2124,"code":2125,"language":2126,"meta":1323,"style":1323},"language-html shiki shiki-themes github-light github-dark-dimmed","\u003Cnav>\n  \u003Ca routerLink=\"\u002Fhome\">Home\u003C\u002Fa>\n  |\n  \u003Ca routerLink=\"\u002Fuser\">User\u003C\u002Fa>\n\u003C\u002Fnav>\n\n\u003Chr \u002F>\n\n\u003Cp>\n  Navigation ID：\n  {{ router.currentNavigation()?.id ?? '目前沒有導航' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  前往：\n  {{ router.currentNavigation()?.initialUrl?.toString() ?? '-' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  Trigger：\n  {{ router.currentNavigation()?.trigger ?? '-' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  目前 URL：\n  {{ router.url }}\n\u003C\u002Fp>\n\n\u003Crouter-outlet \u002F>\n","html",[1220,2128,2129,2139,2164,2169,2189,2198,2202,2212,2216,2224,2229,2234,2242,2246,2254,2259,2264,2272,2276,2284,2289,2294,2302,2306,2314,2319,2324,2332,2336],{"__ignoreMap":1323},[1327,2130,2131,2133,2137],{"class":1329,"line":340},[1327,2132,2074],{"class":1336},[1327,2134,2136],{"class":2135},"sIAta","nav",[1327,2138,2085],{"class":1336},[1327,2140,2141,2144,2147,2151,2154,2157,2160,2162],{"class":1329,"line":329},[1327,2142,2143],{"class":1336},"  \u003C",[1327,2145,2146],{"class":2135},"a",[1327,2148,2150],{"class":2149},"sMOND"," routerLink",[1327,2152,2153],{"class":1336},"=",[1327,2155,2156],{"class":1343},"\"\u002Fhome\"",[1327,2158,2159],{"class":1336},">Home\u003C\u002F",[1327,2161,2146],{"class":2135},[1327,2163,2085],{"class":1336},[1327,2165,2166],{"class":1329,"line":318},[1327,2167,2168],{"class":1336},"  |\n",[1327,2170,2171,2173,2175,2177,2179,2182,2185,2187],{"class":1329,"line":134},[1327,2172,2143],{"class":1336},[1327,2174,2146],{"class":2135},[1327,2176,2150],{"class":2149},[1327,2178,2153],{"class":1336},[1327,2180,2181],{"class":1343},"\"\u002Fuser\"",[1327,2183,2184],{"class":1336},">User\u003C\u002F",[1327,2186,2146],{"class":2135},[1327,2188,2085],{"class":1336},[1327,2190,2191,2194,2196],{"class":1329,"line":122},[1327,2192,2193],{"class":1336},"\u003C\u002F",[1327,2195,2136],{"class":2135},[1327,2197,2085],{"class":1336},[1327,2199,2200],{"class":1329,"line":31},[1327,2201,1382],{"emptyLinePlaceholder":1381},[1327,2203,2204,2206,2209],{"class":1329,"line":17},[1327,2205,2074],{"class":1336},[1327,2207,2208],{"class":2135},"hr",[1327,2210,2211],{"class":1336}," \u002F>\n",[1327,2213,2214],{"class":1329,"line":110},[1327,2215,1382],{"emptyLinePlaceholder":1381},[1327,2217,2218,2220,2222],{"class":1329,"line":76},[1327,2219,2074],{"class":1336},[1327,2221,1216],{"class":2135},[1327,2223,2085],{"class":1336},[1327,2225,2226],{"class":1329,"line":247},[1327,2227,2228],{"class":1336},"  Navigation ID：\n",[1327,2230,2231],{"class":1329,"line":236},[1327,2232,2233],{"class":1336},"  {{ router.currentNavigation()?.id ?? '目前沒有導航' }}\n",[1327,2235,2236,2238,2240],{"class":1329,"line":225},[1327,2237,2193],{"class":1336},[1327,2239,1216],{"class":2135},[1327,2241,2085],{"class":1336},[1327,2243,2244],{"class":1329,"line":214},[1327,2245,1382],{"emptyLinePlaceholder":1381},[1327,2247,2248,2250,2252],{"class":1329,"line":203},[1327,2249,2074],{"class":1336},[1327,2251,1216],{"class":2135},[1327,2253,2085],{"class":1336},[1327,2255,2256],{"class":1329,"line":192},[1327,2257,2258],{"class":1336},"  前往：\n",[1327,2260,2261],{"class":1329,"line":181},[1327,2262,2263],{"class":1336},"  {{ router.currentNavigation()?.initialUrl?.toString() ?? '-' }}\n",[1327,2265,2266,2268,2270],{"class":1329,"line":170},[1327,2267,2193],{"class":1336},[1327,2269,1216],{"class":2135},[1327,2271,2085],{"class":1336},[1327,2273,2274],{"class":1329,"line":159},[1327,2275,1382],{"emptyLinePlaceholder":1381},[1327,2277,2278,2280,2282],{"class":1329,"line":148},[1327,2279,2074],{"class":1336},[1327,2281,1216],{"class":2135},[1327,2283,2085],{"class":1336},[1327,2285,2286],{"class":1329,"line":137},[1327,2287,2288],{"class":1336},"  Trigger：\n",[1327,2290,2291],{"class":1329,"line":125},[1327,2292,2293],{"class":1336},"  {{ router.currentNavigation()?.trigger ?? '-' }}\n",[1327,2295,2296,2298,2300],{"class":1329,"line":113},[1327,2297,2193],{"class":1336},[1327,2299,1216],{"class":2135},[1327,2301,2085],{"class":1336},[1327,2303,2304],{"class":1329,"line":101},[1327,2305,1382],{"emptyLinePlaceholder":1381},[1327,2307,2308,2310,2312],{"class":1329,"line":90},[1327,2309,2074],{"class":1336},[1327,2311,1216],{"class":2135},[1327,2313,2085],{"class":1336},[1327,2315,2316],{"class":1329,"line":79},[1327,2317,2318],{"class":1336},"  目前 URL：\n",[1327,2320,2321],{"class":1329,"line":67},[1327,2322,2323],{"class":1336},"  {{ router.url }}\n",[1327,2325,2326,2328,2330],{"class":1329,"line":56},[1327,2327,2193],{"class":1336},[1327,2329,1216],{"class":2135},[1327,2331,2085],{"class":1336},[1327,2333,2334],{"class":1329,"line":45},[1327,2335,1382],{"emptyLinePlaceholder":1381},[1327,2337,2338,2340,2343],{"class":1329,"line":34},[1327,2339,2074],{"class":1336},[1327,2341,2342],{"class":2135},"router-outlet",[1327,2344,2211],{"class":1336},[1216,2346,2347],{},"Component 中只需要取得 Router：",[1318,2349,2351],{"className":1320,"code":2350,"language":1322,"meta":1323,"style":1323},"import { Component, inject } from '@angular\u002Fcore';\nimport {\n  Router,\n  RouterLink,\n  RouterOutlet,\n} from '@angular\u002Frouter';\n\n@Component({\n  selector: 'app-root',\n  imports: [\n    RouterLink,\n    RouterOutlet,\n  ],\n  templateUrl: '.\u002Fapp.component.html',\n})\nexport class AppComponent {\n  readonly router = inject(Router);\n}\n",[1220,2352,2353,2366,2372,2376,2381,2386,2396,2400,2411,2422,2427,2432,2437,2441,2451,2456,2466,2478],{"__ignoreMap":1323},[1327,2354,2355,2357,2360,2362,2364],{"class":1329,"line":340},[1327,2356,1333],{"class":1332},[1327,2358,2359],{"class":1336}," { Component, inject } ",[1327,2361,1340],{"class":1332},[1327,2363,1344],{"class":1343},[1327,2365,1347],{"class":1336},[1327,2367,2368,2370],{"class":1329,"line":329},[1327,2369,1333],{"class":1332},[1327,2371,1354],{"class":1336},[1327,2373,2374],{"class":1329,"line":318},[1327,2375,1533],{"class":1336},[1327,2377,2378],{"class":1329,"line":134},[1327,2379,2380],{"class":1336},"  RouterLink,\n",[1327,2382,2383],{"class":1329,"line":122},[1327,2384,2385],{"class":1336},"  RouterOutlet,\n",[1327,2387,2388,2390,2392,2394],{"class":1329,"line":31},[1327,2389,1369],{"class":1336},[1327,2391,1340],{"class":1332},[1327,2393,1374],{"class":1343},[1327,2395,1347],{"class":1336},[1327,2397,2398],{"class":1329,"line":17},[1327,2399,1382],{"emptyLinePlaceholder":1381},[1327,2401,2402,2405,2408],{"class":1329,"line":110},[1327,2403,2404],{"class":1336},"@",[1327,2406,2407],{"class":1434},"Component",[1327,2409,2410],{"class":1336},"({\n",[1327,2412,2413,2416,2419],{"class":1329,"line":76},[1327,2414,2415],{"class":1336},"  selector: ",[1327,2417,2418],{"class":1343},"'app-root'",[1327,2420,2421],{"class":1336},",\n",[1327,2423,2424],{"class":1329,"line":247},[1327,2425,2426],{"class":1336},"  imports: [\n",[1327,2428,2429],{"class":1329,"line":236},[1327,2430,2431],{"class":1336},"    RouterLink,\n",[1327,2433,2434],{"class":1329,"line":225},[1327,2435,2436],{"class":1336},"    RouterOutlet,\n",[1327,2438,2439],{"class":1329,"line":214},[1327,2440,1461],{"class":1336},[1327,2442,2443,2446,2449],{"class":1329,"line":203},[1327,2444,2445],{"class":1336},"  templateUrl: ",[1327,2447,2448],{"class":1343},"'.\u002Fapp.component.html'",[1327,2450,2421],{"class":1336},[1327,2452,2453],{"class":1329,"line":192},[1327,2454,2455],{"class":1336},"})\n",[1327,2457,2458,2460,2462,2464],{"class":1329,"line":181},[1327,2459,1405],{"class":1332},[1327,2461,1554],{"class":1332},[1327,2463,1557],{"class":1418},[1327,2465,1354],{"class":1336},[1327,2467,2468,2470,2472,2474,2476],{"class":1329,"line":170},[1327,2469,2018],{"class":1332},[1327,2471,1571],{"class":1570},[1327,2473,1422],{"class":1332},[1327,2475,1576],{"class":1434},[1327,2477,1579],{"class":1336},[1327,2479,2480],{"class":1329,"line":159},[1327,2481,1770],{"class":1336},[1216,2483,2484],{},"一般 Navigation 很快就會完成，因此畫面上的資料可能只會出現很短的時間。",[1216,2486,2487],{},"為了方便觀察，可以加上一個故意延遲兩秒的 Guard：",[1318,2489,2491],{"className":1320,"code":2490,"language":1322,"meta":1323,"style":1323},"import { CanActivateFn } from '@angular\u002Frouter';\n\nexport const delayGuard: CanActivateFn = async () => {\n  await new Promise(\n    resolve => setTimeout(resolve, 2000)\n  );\n\n  return true;\n};\n",[1220,2492,2493,2506,2510,2536,2549,2567,2571,2575,2585],{"__ignoreMap":1323},[1327,2494,2495,2497,2500,2502,2504],{"class":1329,"line":340},[1327,2496,1333],{"class":1332},[1327,2498,2499],{"class":1336}," { CanActivateFn } ",[1327,2501,1340],{"class":1332},[1327,2503,1374],{"class":1343},[1327,2505,1347],{"class":1336},[1327,2507,2508],{"class":1329,"line":329},[1327,2509,1382],{"emptyLinePlaceholder":1381},[1327,2511,2512,2514,2516,2519,2521,2524,2526,2529,2532,2534],{"class":1329,"line":318},[1327,2513,1405],{"class":1332},[1327,2515,1408],{"class":1332},[1327,2517,2518],{"class":1434}," delayGuard",[1327,2520,1415],{"class":1332},[1327,2522,2523],{"class":1418}," CanActivateFn",[1327,2525,1422],{"class":1332},[1327,2527,2528],{"class":1332}," async",[1327,2530,2531],{"class":1336}," () ",[1327,2533,1921],{"class":1332},[1327,2535,1354],{"class":1336},[1327,2537,2538,2541,2544,2547],{"class":1329,"line":134},[1327,2539,2540],{"class":1332},"  await",[1327,2542,2543],{"class":1332}," new",[1327,2545,2546],{"class":1411}," Promise",[1327,2548,1438],{"class":1336},[1327,2550,2551,2554,2556,2559,2562,2565],{"class":1329,"line":122},[1327,2552,2553],{"class":1570},"    resolve",[1327,2555,1611],{"class":1332},[1327,2557,2558],{"class":1434}," setTimeout",[1327,2560,2561],{"class":1336},"(resolve, ",[1327,2563,2564],{"class":1411},"2000",[1327,2566,1936],{"class":1336},[1327,2568,2569],{"class":1329,"line":31},[1327,2570,2052],{"class":1336},[1327,2572,2573],{"class":1329,"line":17},[1327,2574,1382],{"emptyLinePlaceholder":1381},[1327,2576,2577,2580,2583],{"class":1329,"line":110},[1327,2578,2579],{"class":1332},"  return",[1327,2581,2582],{"class":1411}," true",[1327,2584,1347],{"class":1336},[1327,2586,2587],{"class":1329,"line":76},[1327,2588,1466],{"class":1336},[1216,2590,2591],{},"接著把 Guard 加到兩個路由：",[1318,2593,2595],{"className":1320,"code":2594,"language":1322,"meta":1323,"style":1323},"const routes: Routes = [\n  {\n    path: 'home',\n    component: HomeComponent,\n    canActivate: [delayGuard],\n  },\n  {\n    path: 'user',\n    component: UserComponent,\n    canActivate: [delayGuard],\n  },\n];\n",[1220,2596,2597,2614,2619,2629,2634,2639,2644,2648,2657,2662,2666,2670],{"__ignoreMap":1323},[1327,2598,2599,2601,2604,2606,2609,2611],{"class":1329,"line":340},[1327,2600,1029],{"class":1332},[1327,2602,2603],{"class":1411}," routes",[1327,2605,1415],{"class":1332},[1327,2607,2608],{"class":1418}," Routes",[1327,2610,1422],{"class":1332},[1327,2612,2613],{"class":1336}," [\n",[1327,2615,2616],{"class":1329,"line":329},[1327,2617,2618],{"class":1336},"  {\n",[1327,2620,2621,2624,2627],{"class":1329,"line":318},[1327,2622,2623],{"class":1336},"    path: ",[1327,2625,2626],{"class":1343},"'home'",[1327,2628,2421],{"class":1336},[1327,2630,2631],{"class":1329,"line":134},[1327,2632,2633],{"class":1336},"    component: HomeComponent,\n",[1327,2635,2636],{"class":1329,"line":122},[1327,2637,2638],{"class":1336},"    canActivate: [delayGuard],\n",[1327,2640,2641],{"class":1329,"line":31},[1327,2642,2643],{"class":1336},"  },\n",[1327,2645,2646],{"class":1329,"line":17},[1327,2647,2618],{"class":1336},[1327,2649,2650,2652,2655],{"class":1329,"line":110},[1327,2651,2623],{"class":1336},[1327,2653,2654],{"class":1343},"'user'",[1327,2656,2421],{"class":1336},[1327,2658,2659],{"class":1329,"line":76},[1327,2660,2661],{"class":1336},"    component: UserComponent,\n",[1327,2663,2664],{"class":1329,"line":247},[1327,2665,2638],{"class":1336},[1327,2667,2668],{"class":1329,"line":236},[1327,2669,2643],{"class":1336},[1327,2671,2672],{"class":1329,"line":225},[1327,2673,2674],{"class":1336},"];\n",[1216,2676,2677,2678,1230,2681,2684],{},"這樣在 ",[1220,2679,2680],{},"\u002Fhome",[1220,2682,2683],{},"\u002Fuser"," 之間切換時，就能比較清楚地觀察 Navigation 期間的資料。",[1216,2686,2687,2688,2690,2691,1316],{},"例如從 ",[1220,2689,2680],{}," 前往 ",[1220,2692,2683],{},[1318,2694,2699],{"className":2695,"code":2697,"language":2698,"meta":1323},[2696],"language-text","Navigation ID：11\n前往：\u002Fuser\nTrigger：imperative\n目前 URL：\u002Fhome\n","text",[1220,2700,2697],{"__ignoreMap":1323},[1216,2702,2703,2704,2706,2707,2709,2710,2712,2713,1234],{},"此時 Navigation 還沒有完成，所以 ",[1220,2705,2106],{}," 已經記錄這次要前往 ",[1220,2708,2683],{},"，但 ",[1220,2711,2110],{}," 仍然停留在原本的 ",[1220,2714,2680],{},[1216,2716,2717],{},"其中顯示的資訊說明如下：",[1279,2719,2720,2726,2732],{},[1282,2721,2722,2725],{},[1220,2723,2724],{},"id","：這次 Navigation 的流水號",[1282,2727,2728,2731],{},[1220,2729,2730],{},"initialUrl","：Navigation 一開始要前往的 URL",[1282,2733,2734,2737,2738,2741,2742,1789,2745,2748,2749],{},[1220,2735,2736],{},"trigger","：Navigation 的觸發方式，例如 ",[1220,2739,2740],{},"imperative"," 代表透過 ",[1220,2743,2744],{},"routerLink",[1220,2746,2747],{},"router.navigate()"," 主動導航；使用瀏覽器上一頁、下一頁時則可能是 ",[1220,2750,2751],{},"popstate",[1216,2753,2754],{},[1265,2755],{"alt":2756,"src":2757},"一般導航與加入 Guard 後的 currentNavigation() 變化","https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790511833\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fcurrent-navigation-observation.png",[2759,2760,2761],"blockquote",{},[1216,2762,2763,2764],{},"這部分可以參考 ",[2146,2765,2769],{"href":2766,"rel":2767},"https:\u002F\u002Fstackblitz.com\u002Fedit\u002Fldbybozv-tcz79wqj?file=src%2Fmain.ts",[2768],"nofollow","StackBlitz 範例",[1216,2771,2772,2773,2775,2776,2778,2779,2781],{},"等導航完成後，",[1220,2774,2106],{}," 會回到 ",[1220,2777,1843],{},"，而 ",[1220,2780,2110],{}," 則更新成新的 URL：",[1318,2783,2785],{"className":1320,"code":2784,"language":1322,"meta":1323,"style":1323},"this.router.currentNavigation();\n\u002F\u002F null\n\nthis.router.url;\n\u002F\u002F '\u002Fuser'\n",[1220,2786,2787,2797,2803,2807,2814],{"__ignoreMap":1323},[1327,2788,2789,2791,2793,2795],{"class":1329,"line":340},[1327,2790,1128],{"class":1411},[1327,2792,1827],{"class":1336},[1327,2794,1949],{"class":1434},[1327,2796,1833],{"class":1336},[1327,2798,2799],{"class":1329,"line":329},[1327,2800,2802],{"class":2801},"sDN9O","\u002F\u002F null\n",[1327,2804,2805],{"class":1329,"line":318},[1327,2806,1382],{"emptyLinePlaceholder":1381},[1327,2808,2809,2811],{"class":1329,"line":134},[1327,2810,1128],{"class":1411},[1327,2812,2813],{"class":1336},".router.url;\n",[1327,2815,2816],{"class":1329,"line":122},[1327,2817,2818],{"class":2801},"\u002F\u002F '\u002Fuser'\n",[2112,2820,2822],{"id":2821},"用-currentnavigation-判斷頁面切換","用 currentNavigation() 判斷頁面切換",[1216,2824,2825,2826,2828],{},"因為 ",[1220,2827,2106],{}," 只在 Navigation 期間有值，所以也可以直接拿來判斷頁面是否正在切換：",[1318,2830,2832],{"className":1320,"code":2831,"language":1322,"meta":1323,"style":1323},"readonly isNavigating = computed(\n  () => this.router.currentNavigation() !== null\n);\n",[1220,2833,2834,2845,2864],{"__ignoreMap":1323},[1327,2835,2836,2839,2841,2843],{"class":1329,"line":340},[1327,2837,2838],{"class":1336},"readonly isNavigating ",[1327,2840,2153],{"class":1332},[1327,2842,2025],{"class":1434},[1327,2844,1438],{"class":1336},[1327,2846,2847,2850,2852,2854,2856,2858,2860,2862],{"class":1329,"line":329},[1327,2848,2849],{"class":1336},"  () ",[1327,2851,1921],{"class":1332},[1327,2853,1824],{"class":1411},[1327,2855,1827],{"class":1336},[1327,2857,1949],{"class":1434},[1327,2859,1873],{"class":1336},[1327,2861,1876],{"class":1332},[1327,2863,2047],{"class":1411},[1327,2865,2866],{"class":1329,"line":318},[1327,2867,1646],{"class":1336},[1216,2869,2870],{},"Template 再根據這個狀態決定是否顯示 Loading：",[1318,2872,2874],{"className":1320,"code":2873,"language":1322,"meta":1323,"style":1323},"@if (isNavigating()) {\n  \u003Capp-page-loading \u002F>\n}\n\n\u003Crouter-outlet \u002F>\n",[1220,2875,2876,2892,2913,2917,2921],{"__ignoreMap":1323},[1327,2877,2878,2880,2883,2886,2889],{"class":1329,"line":340},[1327,2879,2404],{"class":1336},[1327,2881,2882],{"class":1434},"if",[1327,2884,2885],{"class":1336}," (",[1327,2887,2888],{"class":1434},"isNavigating",[1327,2890,2891],{"class":1336},"()) {\n",[1327,2893,2894,2896,2899,2902,2905,2907,2910],{"class":1329,"line":329},[1327,2895,2143],{"class":1332},[1327,2897,2898],{"class":1336},"app",[1327,2900,2901],{"class":1332},"-",[1327,2903,2904],{"class":1336},"page",[1327,2906,2901],{"class":1332},[1327,2908,2909],{"class":1336},"loading ",[1327,2911,2912],{"class":1332},"\u002F>\n",[1327,2914,2915],{"class":1329,"line":318},[1327,2916,1770],{"class":1336},[1327,2918,2919],{"class":1329,"line":134},[1327,2920,1382],{"emptyLinePlaceholder":1381},[1327,2922,2923,2925,2928,2930,2933],{"class":1329,"line":122},[1327,2924,2074],{"class":1332},[1327,2926,2927],{"class":1336},"router",[1327,2929,2901],{"class":1332},[1327,2931,2932],{"class":1336},"outlet ",[1327,2934,2912],{"class":1332},[1216,2936,2937],{},"這裡的 Loading 代表的是「頁面正在切換」，適合放在 Layout 或 App Shell 層級，和頁面內某支 API 的 Loading 是不同的狀態。",[1250,2939,2941],{"id":2940},"lastsuccessfulnavigation","lastSuccessfulNavigation",[1216,2943,2944,2945,2947],{},"除了正在進行的 Navigation，Router 也提供 ",[1220,2946,2941],{},"，用來取得最近一次成功完成的 Navigation。",[1216,2949,2950,2951,2953,2954,2957],{},"在 Angular 20 中，",[1220,2952,2941],{}," 還是一般的 ",[1220,2955,2956],{},"Navigation | null","；到了 Angular 21，則改成：",[1318,2959,2960],{"className":1320,"code":2064,"language":1322,"meta":1323,"style":1323},[1220,2961,2962],{"__ignoreMap":1323},[1327,2963,2964,2966,2968,2970,2972,2974],{"class":1329,"line":340},[1327,2965,2071],{"class":1336},[1327,2967,2074],{"class":1332},[1327,2969,2077],{"class":1336},[1327,2971,2080],{"class":1332},[1327,2973,1879],{"class":1411},[1327,2975,2085],{"class":1332},[1216,2977,2978],{},"例如：",[1318,2980,2982],{"className":1320,"code":2981,"language":1322,"meta":1323,"style":1323},"private readonly router = inject(Router);\n\nreadonly lastNavigation =\n  this.router.lastSuccessfulNavigation;\n",[1220,2983,2984,2995,2999,3007],{"__ignoreMap":1323},[1327,2985,2986,2989,2991,2993],{"class":1329,"line":340},[1327,2987,2988],{"class":1336},"private readonly router ",[1327,2990,2153],{"class":1332},[1327,2992,1576],{"class":1434},[1327,2994,1579],{"class":1336},[1327,2996,2997],{"class":1329,"line":329},[1327,2998,1382],{"emptyLinePlaceholder":1381},[1327,3000,3001,3004],{"class":1329,"line":318},[1327,3002,3003],{"class":1336},"readonly lastNavigation ",[1327,3005,3006],{"class":1332},"=\n",[1327,3008,3009,3011],{"class":1329,"line":134},[1327,3010,1866],{"class":1411},[1327,3012,3013],{"class":1336},".router.lastSuccessfulNavigation;\n",[1216,3015,3016],{},"讀取時直接呼叫 Signal：",[1318,3018,3020],{"className":1320,"code":3019,"language":1322,"meta":1323,"style":1323},"const navigation =\n  this.router.lastSuccessfulNavigation();\n",[1220,3021,3022,3030],{"__ignoreMap":1323},[1327,3023,3024,3026,3028],{"class":1329,"line":340},[1327,3025,1029],{"class":1332},[1327,3027,1819],{"class":1411},[1327,3029,1861],{"class":1332},[1327,3031,3032,3034,3036,3038],{"class":1329,"line":329},[1327,3033,1866],{"class":1411},[1327,3035,1827],{"class":1336},[1327,3037,2941],{"class":1434},[1327,3039,1833],{"class":1336},[1216,3041,3042,3043,3045],{},"它和 ",[1220,3044,2106],{}," 關注的時間點不同：",[1279,3047,3048,3055],{},[1282,3049,3050,3052,3053],{},[1220,3051,2106],{},"：正在執行的 Navigation，結束後會回到 ",[1220,3054,1843],{},[1282,3056,3057,3060],{},[1220,3058,3059],{},"lastSuccessfulNavigation()","：最近一次成功完成的 Navigation，會保留到下一次成功導航完成",[1216,3062,3063,3064,2690,3067,3070],{},"以從 ",[1220,3065,3066],{},"\u002Fusers",[1220,3068,3069],{},"\u002Forders"," 為例：",[1216,3072,3073],{},[1265,3074],{"alt":3075,"src":3076},"currentNavigation 與 lastSuccessfulNavigation 在導航前後的值","https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509047\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fnavigation-signals-timeline.png",[1250,3078,3080],{"id":3079},"isactive","isActive()",[1216,3082,3083,3084,3086],{},"除了 Navigation 本身，Angular Router 也提供 Signal-based 的 ",[1220,3085,3080],{},"，用來判斷指定 URL 是否符合目前路由。",[1216,3088,3089,3090,3092],{},"例如側邊選單希望 ",[1220,3091,3066],{}," 底下的頁面都維持 Active：",[1318,3094,3096],{"className":1320,"code":3095,"language":1322,"meta":1323,"style":1323},"import { inject } from '@angular\u002Fcore';\nimport { isActive, Router } from '@angular\u002Frouter';\n\nexport class SidebarComponent {\n  private readonly router = inject(Router);\n\n  readonly isUsersActive = isActive(\n    '\u002Fusers',\n    this.router\n  );\n}\n",[1220,3097,3098,3110,3123,3127,3138,3152,3156,3170,3177,3184,3188],{"__ignoreMap":1323},[1327,3099,3100,3102,3104,3106,3108],{"class":1329,"line":340},[1327,3101,1333],{"class":1332},[1327,3103,1496],{"class":1336},[1327,3105,1340],{"class":1332},[1327,3107,1344],{"class":1343},[1327,3109,1347],{"class":1336},[1327,3111,3112,3114,3117,3119,3121],{"class":1329,"line":329},[1327,3113,1333],{"class":1332},[1327,3115,3116],{"class":1336}," { isActive, Router } ",[1327,3118,1340],{"class":1332},[1327,3120,1374],{"class":1343},[1327,3122,1347],{"class":1336},[1327,3124,3125],{"class":1329,"line":318},[1327,3126,1382],{"emptyLinePlaceholder":1381},[1327,3128,3129,3131,3133,3136],{"class":1329,"line":134},[1327,3130,1405],{"class":1332},[1327,3132,1554],{"class":1332},[1327,3134,3135],{"class":1418}," SidebarComponent",[1327,3137,1354],{"class":1336},[1327,3139,3140,3142,3144,3146,3148,3150],{"class":1329,"line":122},[1327,3141,1564],{"class":1332},[1327,3143,1567],{"class":1332},[1327,3145,1571],{"class":1570},[1327,3147,1422],{"class":1332},[1327,3149,1576],{"class":1434},[1327,3151,1579],{"class":1336},[1327,3153,3154],{"class":1329,"line":31},[1327,3155,1382],{"emptyLinePlaceholder":1381},[1327,3157,3158,3160,3163,3165,3168],{"class":1329,"line":17},[1327,3159,2018],{"class":1332},[1327,3161,3162],{"class":1570}," isUsersActive",[1327,3164,1422],{"class":1332},[1327,3166,3167],{"class":1434}," isActive",[1327,3169,1438],{"class":1336},[1327,3171,3172,3175],{"class":1329,"line":110},[1327,3173,3174],{"class":1343},"    '\u002Fusers'",[1327,3176,2421],{"class":1336},[1327,3178,3179,3181],{"class":1329,"line":76},[1327,3180,1596],{"class":1411},[1327,3182,3183],{"class":1336},".router\n",[1327,3185,3186],{"class":1329,"line":247},[1327,3187,2052],{"class":1336},[1327,3189,3190],{"class":1329,"line":236},[1327,3191,1770],{"class":1336},[1216,3193,3194,3196],{},[1220,3195,3080],{}," 回傳：",[1318,3198,3200],{"className":1320,"code":3199,"language":1322,"meta":1323,"style":1323},"Signal\u003Cboolean>\n",[1220,3201,3202],{"__ignoreMap":1323},[1327,3203,3204,3206,3208,3211],{"class":1329,"line":340},[1327,3205,2071],{"class":1336},[1327,3207,2074],{"class":1332},[1327,3209,3210],{"class":1336},"boolean",[1327,3212,2085],{"class":1332},[1216,3214,3215],{},"Template 可以直接使用：",[1318,3217,3219],{"className":2124,"code":3218,"language":2126,"meta":1323,"style":1323},"\u003Ca\n  routerLink=\"\u002Fusers\"\n  [class.active]=\"isUsersActive()\"\n>\n  使用者\n\u003C\u002Fa>\n",[1220,3220,3221,3228,3238,3248,3252,3257],{"__ignoreMap":1323},[1327,3222,3223,3225],{"class":1329,"line":340},[1327,3224,2074],{"class":1336},[1327,3226,3227],{"class":2135},"a\n",[1327,3229,3230,3233,3235],{"class":1329,"line":329},[1327,3231,3232],{"class":2149},"  routerLink",[1327,3234,2153],{"class":1336},[1327,3236,3237],{"class":1343},"\"\u002Fusers\"\n",[1327,3239,3240,3243,3245],{"class":1329,"line":318},[1327,3241,3242],{"class":2149},"  [class.active]",[1327,3244,2153],{"class":1336},[1327,3246,3247],{"class":1343},"\"isUsersActive()\"\n",[1327,3249,3250],{"class":1329,"line":134},[1327,3251,2085],{"class":1336},[1327,3253,3254],{"class":1329,"line":122},[1327,3255,3256],{"class":1336},"  使用者\n",[1327,3258,3259,3261,3263],{"class":1329,"line":31},[1327,3260,2193],{"class":1336},[1327,3262,2146],{"class":2135},[1327,3264,2085],{"class":1336},[1216,3266,3267,3268,3270,3271,3274,3275,3278,3279,1223,3282,3285],{},"沒有傳入比對選項時，",[1220,3269,3080],{}," 的 ",[1220,3272,3273],{},"paths"," 預設是 ",[1220,3276,3277],{},"'subset'","，因此 ",[1220,3280,3281],{},"\u002Fusers\u002F1",[1220,3283,3284],{},"\u002Fusers\u002F2"," 這類子路由也會算是 Active。",[1216,3287,3288],{},"其他預設值則是：",[1318,3290,3292],{"className":1320,"code":3291,"language":1322,"meta":1323,"style":1323},"{\n  paths: 'subset',\n  queryParams: 'subset',\n  matrixParams: 'ignored',\n  fragment: 'ignored',\n}\n",[1220,3293,3294,3299,3311,3322,3334,3345],{"__ignoreMap":1323},[1327,3295,3296],{"class":1329,"line":340},[1327,3297,3298],{"class":1336},"{\n",[1327,3300,3301,3304,3307,3309],{"class":1329,"line":329},[1327,3302,3303],{"class":1418},"  paths",[1327,3305,3306],{"class":1336},": ",[1327,3308,3277],{"class":1343},[1327,3310,2421],{"class":1336},[1327,3312,3313,3316,3318,3320],{"class":1329,"line":318},[1327,3314,3315],{"class":1418},"  queryParams",[1327,3317,3306],{"class":1336},[1327,3319,3277],{"class":1343},[1327,3321,2421],{"class":1336},[1327,3323,3324,3327,3329,3332],{"class":1329,"line":134},[1327,3325,3326],{"class":1418},"  matrixParams",[1327,3328,3306],{"class":1336},[1327,3330,3331],{"class":1343},"'ignored'",[1327,3333,2421],{"class":1336},[1327,3335,3336,3339,3341,3343],{"class":1329,"line":122},[1327,3337,3338],{"class":1418},"  fragment",[1327,3340,3306],{"class":1336},[1327,3342,3331],{"class":1343},[1327,3344,2421],{"class":1336},[1327,3346,3347],{"class":1329,"line":31},[1327,3348,1770],{"class":1336},[1216,3350,3351],{},"如果要自己控制比對方式，可以傳入第三個參數：",[1318,3353,3355],{"className":1320,"code":3354,"language":1322,"meta":1323,"style":1323},"readonly isUsersActive = isActive(\n  '\u002Fusers',\n  this.router,\n  {\n    paths: 'subset',\n    queryParams: 'ignored',\n    matrixParams: 'ignored',\n    fragment: 'ignored',\n  }\n);\n",[1220,3356,3357,3368,3375,3382,3386,3395,3404,3413,3422,3426],{"__ignoreMap":1323},[1327,3358,3359,3362,3364,3366],{"class":1329,"line":340},[1327,3360,3361],{"class":1336},"readonly isUsersActive ",[1327,3363,2153],{"class":1332},[1327,3365,3167],{"class":1434},[1327,3367,1438],{"class":1336},[1327,3369,3370,3373],{"class":1329,"line":329},[1327,3371,3372],{"class":1343},"  '\u002Fusers'",[1327,3374,2421],{"class":1336},[1327,3376,3377,3379],{"class":1329,"line":318},[1327,3378,1866],{"class":1411},[1327,3380,3381],{"class":1336},".router,\n",[1327,3383,3384],{"class":1329,"line":134},[1327,3385,2618],{"class":1336},[1327,3387,3388,3391,3393],{"class":1329,"line":122},[1327,3389,3390],{"class":1336},"    paths: ",[1327,3392,3277],{"class":1343},[1327,3394,2421],{"class":1336},[1327,3396,3397,3400,3402],{"class":1329,"line":31},[1327,3398,3399],{"class":1336},"    queryParams: ",[1327,3401,3331],{"class":1343},[1327,3403,2421],{"class":1336},[1327,3405,3406,3409,3411],{"class":1329,"line":17},[1327,3407,3408],{"class":1336},"    matrixParams: ",[1327,3410,3331],{"class":1343},[1327,3412,2421],{"class":1336},[1327,3414,3415,3418,3420],{"class":1329,"line":110},[1327,3416,3417],{"class":1336},"    fragment: ",[1327,3419,3331],{"class":1343},[1327,3421,2421],{"class":1336},[1327,3423,3424],{"class":1329,"line":76},[1327,3425,1764],{"class":1336},[1327,3427,3428],{"class":1329,"line":247},[1327,3429,1646],{"class":1336},[1216,3431,3432],{},"這幾個選項分別控制 URL 不同部分的比對方式：",[1279,3434,3435,3456,3469,3482],{},[1282,3436,3437,3440,3441,3443,3444,1223,3446,1223,3448,3451,3452,3455],{},[1220,3438,3439],{},"paths: 'subset'","：目前 URL 位於 ",[1220,3442,3066],{}," 底下就算符合，例如 ",[1220,3445,3066],{},[1220,3447,3281],{},[1220,3449,3450],{},"\u002Fusers\u002F1\u002Fedit","；改成 ",[1220,3453,3454],{},"'exact'"," 則需要 Path 完全一致",[1282,3457,3458,3461,3462,1230,3465,3468],{},[1220,3459,3460],{},"queryParams: 'ignored'","：忽略 Query Params，因此 ",[1220,3463,3464],{},"\u002Fusers?page=1",[1220,3466,3467],{},"\u002Fusers?page=2"," 不會影響 Active 結果",[1282,3470,3471,3474,3475,3478,3479],{},[1220,3472,3473],{},"matrixParams: 'ignored'","：忽略 Matrix Params，例如 ",[1220,3476,3477],{},"\u002Fusers;type=admin"," 中的 ",[1220,3480,3481],{},"type=admin",[1282,3483,3484,3487,3488,3491,3492],{},[1220,3485,3486],{},"fragment: 'ignored'","：忽略 Fragment，因此 ",[1220,3489,3490],{},"\u002Fusers#profile"," 仍然會視為 ",[1220,3493,3066],{},[1216,3495,3496],{},"過去 Router 也有：",[1318,3498,3500],{"className":1320,"code":3499,"language":1322,"meta":1323,"style":1323},"this.router.isActive(...)\n",[1220,3501,3502],{"__ignoreMap":1323},[1327,3503,3504,3506,3508,3511,3513,3516],{"class":1329,"line":340},[1327,3505,1128],{"class":1411},[1327,3507,1827],{"class":1336},[1327,3509,3510],{"class":1434},"isActive",[1327,3512,1605],{"class":1336},[1327,3514,3515],{"class":1332},"...",[1327,3517,1936],{"class":1336},[1216,3519,3520,3521,3523],{},"但它回傳的是一般的 ",[1220,3522,3210],{},"，只代表執行當下的結果。",[1216,3525,3526,3527,3529,3530,3533],{},"新的 ",[1220,3528,3080],{}," 則回傳 ",[1220,3531,3532],{},"Signal\u003Cboolean>","，Router State 改變時會重新計算，從 Angular 21.1 起列為正式 API。",[2112,3535,3537],{"id":3536},"isactive-與-routerlinkactive","isActive() 與 RouterLinkActive",[1216,3539,3540,3541,3544],{},"如果只是替導覽連結加上 Active 樣式，原本的 ",[1220,3542,3543],{},"RouterLinkActive"," 就已經足夠：",[1318,3546,3548],{"className":2124,"code":3547,"language":2126,"meta":1323,"style":1323},"\u003Ca\n  routerLink=\"\u002Fusers\"\n  routerLinkActive=\"active\"\n  ariaCurrentWhenActive=\"page\"\n>\n  使用者\n\u003C\u002Fa>\n",[1220,3549,3550,3556,3564,3574,3584,3588,3592],{"__ignoreMap":1323},[1327,3551,3552,3554],{"class":1329,"line":340},[1327,3553,2074],{"class":1336},[1327,3555,3227],{"class":2135},[1327,3557,3558,3560,3562],{"class":1329,"line":329},[1327,3559,3232],{"class":2149},[1327,3561,2153],{"class":1336},[1327,3563,3237],{"class":1343},[1327,3565,3566,3569,3571],{"class":1329,"line":318},[1327,3567,3568],{"class":2149},"  routerLinkActive",[1327,3570,2153],{"class":1336},[1327,3572,3573],{"class":1343},"\"active\"\n",[1327,3575,3576,3579,3581],{"class":1329,"line":134},[1327,3577,3578],{"class":2149},"  ariaCurrentWhenActive",[1327,3580,2153],{"class":1336},[1327,3582,3583],{"class":1343},"\"page\"\n",[1327,3585,3586],{"class":1329,"line":122},[1327,3587,2085],{"class":1336},[1327,3589,3590],{"class":1329,"line":31},[1327,3591,3256],{"class":1336},[1327,3593,3594,3596,3598],{"class":1329,"line":17},[1327,3595,2193],{"class":1336},[1327,3597,2146],{"class":2135},[1327,3599,2085],{"class":1336},[1216,3601,3602,3604,3605,1234],{},[1220,3603,3543],{}," 是 Directive，適合直接在 Template 上處理 class 與 ",[1220,3606,3607],{},"aria-current",[1216,3609,3610,3612],{},[1220,3611,3080],{}," 則回傳 Signal，因此 Active 結果還可以在 TypeScript 中繼續參與其他狀態推導。",[1216,3614,3615,3616,3618],{},"例如根據目前是否位於 ",[1220,3617,3066],{}," 底下，決定區塊標題：",[1318,3620,3622],{"className":1320,"code":3621,"language":1322,"meta":1323,"style":1323},"readonly sectionTitle = computed(() =>\n  this.isUsersActive() ? '使用者管理' : '總覽'\n);\n",[1220,3623,3624,3638,3662],{"__ignoreMap":1323},[1327,3625,3626,3629,3631,3633,3635],{"class":1329,"line":340},[1327,3627,3628],{"class":1336},"readonly sectionTitle ",[1327,3630,2153],{"class":1332},[1327,3632,2025],{"class":1434},[1327,3634,1918],{"class":1336},[1327,3636,3637],{"class":1332},"=>\n",[1327,3639,3640,3642,3645,3648,3650,3653,3656,3659],{"class":1329,"line":329},[1327,3641,1866],{"class":1411},[1327,3643,3644],{"class":1336},".",[1327,3646,3647],{"class":1434},"isUsersActive",[1327,3649,1873],{"class":1336},[1327,3651,3652],{"class":1332},"?",[1327,3654,3655],{"class":1343}," '使用者管理'",[1327,3657,3658],{"class":1332}," :",[1327,3660,3661],{"class":1343}," '總覽'\n",[1327,3663,3664],{"class":1329,"line":318},[1327,3665,1646],{"class":1336},[1216,3667,3668,3669,3671,3672,3675,3676,3679],{},"當路由位於 ",[1220,3670,3066],{}," 或它的子路由時，",[1220,3673,3674],{},"sectionTitle()"," 會得到 ",[1220,3677,3678],{},"使用者管理","；切換到其他路由後則會重新計算。",[1216,3681,3682],{},[1265,3683],{"alt":3684,"src":3685},"isActive() 的 subset 比對，以及與 RouterLinkActive 的選擇","https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790510392\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fis-active-guide.png",[1216,3687,3688,3689,3691,3692,1234],{},"單純處理導覽樣式時，使用 ",[1220,3690,3543],{}," 會比較直接；如果 Active 結果還要參與其他程式邏輯，再使用 ",[1220,3693,3080],{},[1250,3695,3697],{"id":3696},"router-event-與-router-state","Router Event 與 Router State",[1216,3699,3700],{},"Angular Router 現在可以從「事件」與「狀態」兩個方向處理導航資訊：",[1279,3702,3703,3715,3720,3725],{},[1282,3704,3705,3707,3708,1223,3710,1223,3712,3714],{},[1220,3706,1240],{},"：處理 ",[1220,3709,1222],{},[1220,3711,1226],{},[1220,3713,1233],{}," 等導航事件",[1282,3716,3717,3719],{},[1220,3718,2106],{},"：取得正在進行的 Navigation",[1282,3721,3722,3724],{},[1220,3723,3059],{},"：取得最近一次成功完成的 Navigation",[1282,3726,3727,3729],{},[1220,3728,3080],{},"：判斷指定 URL 是否符合目前路由",[1216,3731,3732,3733,3735],{},"如果需要知道某件事情「什麼時候發生」，例如記錄頁面瀏覽、追蹤 Navigation Error，使用 ",[1220,3734,1240],{}," 會比較適合。",[1216,3737,3738,3739,3741],{},"如果需要的是「目前是什麼狀態」，例如頁面是否正在切換、目前是否位於 ",[1220,3740,3066],{}," 底下，就可以直接從對應的 Signal API 取得。",[1250,3743,3744],{"id":3744},"本日結語",[1216,3746,3747],{},"Router 開始提供 Signal API 後，改變的不只是 API 寫法，而是部分 Router State 本身變成了可以直接追蹤的響應式狀態。",[1216,3749,3750,3752,3753,3755,3756,3758],{},[1220,3751,2106],{}," 代表正在進行的 Navigation，",[1220,3754,3059],{}," 保留最近一次成功完成的 Navigation，而 ",[1220,3757,3080],{}," 則讓 URL 比對結果也能直接作為 Signal 使用。",[1216,3760,3761,3763],{},[1220,3762,1240],{}," 仍然負責事件發生的時間點；Signal API 則負責讀取 Router 當下的狀態。理解這兩者的差別後，就可以依照實際需求選擇適合的方式，而不需要把所有路由狀態都從 Event Stream 中重新整理一次。",[2112,3765,3766],{"id":3766},"資料來源",[1279,3768,3769,3783,3795,3810,3820],{},[1282,3770,3771,3776,3777,3779,3780,3782],{},[2146,3772,3775],{"href":3773,"rel":3774},"https:\u002F\u002Fangular.dev\u002Fguide\u002Frouting\u002Flifecycle-and-events",[2768],"Angular Router 官方文件：Router Lifecycle and Events","：Router Event 的種類、",[1220,3778,1315],{},"，以及用 ",[1220,3781,2106],{}," 做 Loading 的範例。",[1282,3784,3785,1316,3790,1230,3792,3794],{},[2146,3786,3789],{"href":3787,"rel":3788},"https:\u002F\u002Fangular.dev\u002Fguide\u002Frouting\u002Fread-route-state",[2768],"Angular Router 官方文件：Read route state",[1220,3791,3543],{},[1220,3793,3080],{}," 的用法。",[1282,3796,3797,1316,3802,1223,3804,3806,3807,3809],{},[2146,3798,3801],{"href":3799,"rel":3800},"https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Frouter.ts",[2768],"Angular 原始碼：router.ts",[1220,3803,1949],{},[1220,3805,2941],{}," 的型別，以及 ",[1220,3808,1887],{}," 自 20.2 起 Deprecated。",[1282,3811,3812,1316,3817,3819],{},[2146,3813,3816],{"href":3814,"rel":3815},"https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Furl_tree.ts",[2768],"Angular 原始碼：url_tree.ts",[1220,3818,3080],{}," 的比對預設值與實作。",[1282,3821,3822,1316,3827,3829,3830,3832],{},[2146,3823,3826],{"href":3824,"rel":3825},"https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Fdirectives\u002Frouter_link_active.ts",[2768],"Angular 原始碼：router_link_active.ts",[1220,3828,3543],{}," 內部透過 ",[1220,3831,3080],{}," 判斷路由狀態。",[3834,3835,3836],"style",{},"html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .stnAF, html code.shiki .stnAF{--shiki-default:#E36209;--shiki-dark:#F69D50}html pre.shiki code .sIAta, html code.shiki .sIAta{--shiki-default:#22863A;--shiki-dark:#8DDB8C}html pre.shiki code .sMOND, html code.shiki .sMOND{--shiki-default:#6F42C1;--shiki-dark:#6CB6FF}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}",{"title":1323,"searchDepth":134,"depth":134,"links":3838},[3839,3840,3841,3845,3846,3849,3850],{"id":1252,"depth":329,"text":1252},{"id":1477,"depth":329,"text":1478},{"id":1801,"depth":329,"text":1802,"children":3842},[3843,3844],{"id":2114,"depth":318,"text":2115},{"id":2821,"depth":318,"text":2822},{"id":2940,"depth":329,"text":2941},{"id":3079,"depth":329,"text":3080,"children":3847},[3848],{"id":3536,"depth":318,"text":3537},{"id":3696,"depth":329,"text":3697},{"id":3744,"depth":329,"text":3744,"children":3851},[3852],{"id":3766,"depth":318,"text":3766},[11,13],"md",{},"---\ntitle: \"Day 13：從 Router Event 到 Signal\"\ndate: 2026-09-27 20:37:55\ntags:\n  - \"iThome 鐵人賽\"\n  - \"2026 鐵人賽\"\n  - \"Angular\"\ncategories:\n  - \"iThome 鐵人賽\"\n  - \"Angular\"\ndescription: \"路由狀態管理在前端開發中比較少被單獨拿出來討論，但在 Angular 裡，一次路由導航其實會經過不少階段，也會在過程中發出不同的 Router Event，例如 NavigationStart、NavigationEnd…\"\ncanonical: https:\u002F\u002Fithelp.ithome.com.tw\u002Farticles\u002F10417948\nseries:\n  name: \"2026 iThome 鐵人賽：Angular 22 Signal 進化論\"\n  url: https:\u002F\u002Fithelp.ithome.com.tw\u002Fusers\u002F20116554\u002Fironman\u002F9939\n  day: 13\n---\n\n路由狀態管理在前端開發中比較少被單獨拿出來討論，但在 Angular 裡，一次路由導航其實會經過不少階段，也會在過程中發出不同的 Router Event，例如 `NavigationStart`、`NavigationEnd`、`NavigationCancel` 與 `NavigationError`。\n\n過去要觀察這些狀態，通常會透過 `Router.events` 取得事件，再搭配 `Router.getCurrentNavigation()` 讀取正在執行的 Navigation。\n\n隨著 Angular 持續導入 Signal，Router 也開始提供對應的 Signal API。這篇就從導航事件開始，看看過去怎麼處理 Router State，以及現在有哪些狀態可以直接透過 Signal 取得。\n\n## 路由狀態介紹\n\n在 Angular 中，路由導航不只是把一個網址換成另一個網址。\n\n從使用者點擊連結開始，到新的頁面完成導航，中間可能會經過路由比對、Guard 檢查、Resolver 執行、Lazy Loading 與元件啟用等階段。\n\nAngular Router 會在這些階段發出對應的事件，讓我們知道這次 Navigation 執行到哪個位置。\n\n![Angular Router 導航事件流程](https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509053\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Frouter-navigation-flow-expanded.png)\n\n一次導航通常會從 `NavigationStart` 開始，接著進行路由比對。如果有設定 Guard 或 Resolver，也會進入對應的檢查與資料解析流程。\n\n最後會進入其中一種結果：\n\n- `NavigationEnd`：導航成功完成\n- `NavigationCancel`：導航被取消\n- `NavigationError`：導航過程發生錯誤\n- `NavigationSkipped`：Router 判斷這次導航不需要重新執行，例如再次導航到相同 URL\n\n實際流程不一定每次都會經過所有階段，例如沒有設定 Guard 或 Resolver 時，就不會進入對應的處理流程。\n\n如果想直接觀察 Router 發出的事件，可以在 `provideRouter()` 加上 `withDebugTracing()`：\n\n```ts\nimport { ApplicationConfig } from '@angular\u002Fcore';\nimport {\n  provideRouter,\n  withDebugTracing,\n} from '@angular\u002Frouter';\n\nimport { routes } from '.\u002Fapp.routes';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter(\n      routes,\n      withDebugTracing()\n    ),\n  ],\n};\n```\n\n啟用後，導航期間的 Router Event 都會輸出到瀏覽器 Console，方便查看實際執行順序。\n\n`withDebugTracing()` 主要用於開發與除錯，正式環境通常不需要開啟。\n\n## 透過 Router.events 監聽路由事件\n\nAngular Router 提供 `Router.events`，可以取得 Navigation 過程中發出的事件。\n\n```ts\nimport { inject } from '@angular\u002Fcore';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular\u002Frouter';\n\nexport class AppComponent {\n  private readonly router = inject(Router);\n\n  constructor() {\n    this.router.events.subscribe(event => {\n      if (event instanceof NavigationStart) {\n        console.log('開始導航');\n      }\n\n      if (event instanceof NavigationEnd) {\n        console.log('導航完成');\n      }\n\n      if (event instanceof NavigationCancel) {\n        console.log('導航取消');\n      }\n\n      if (event instanceof NavigationError) {\n        console.log('導航發生錯誤');\n      }\n    });\n  }\n}\n```\n\n`Router.events` 是一個 Observable，會隨著 Navigation 進行持續發出不同類型的 Router Event。\n\n它適合用在需要針對特定事件處理的情境，例如頁面瀏覽紀錄、錯誤追蹤，或觀察 Guard、Resolver 的執行狀態。\n\n如果要顯示頁面切換中的 Loading，也可以在 `NavigationStart` 時開啟，再於 `NavigationEnd`、`NavigationCancel` 或 `NavigationError` 時關閉。\n\n不過，如果只是想知道「目前是否有 Navigation 正在進行」，就不一定要自己從多個事件整理出狀態。過去可以透過 `Router.getCurrentNavigation()` 直接讀取目前正在執行的 Navigation。\n\n## 從 getCurrentNavigation() 到 currentNavigation Signal\n\n`Router.getCurrentNavigation()` 會回傳目前正在執行的 Navigation：\n\n```ts\nconst navigation = this.router.getCurrentNavigation();\n```\n\nNavigation 進行期間會取得對應的 `Navigation` 物件，沒有導航正在執行時則會回傳 `null`。\n\n例如可以用來判斷路由是否正在切換：\n\n```ts\nconst isNavigating =\n  this.router.getCurrentNavigation() !== null;\n```\n\n不過 `getCurrentNavigation()` 只會讀取執行當下的值，本身不具備響應式能力。\n\n當 Router 之後開始或完成另一個 Navigation，前面讀到的結果不會自動更新，因此過去常會搭配 `Router.events`，在事件發生時重新讀取：\n\n```ts\nthis.router.events.pipe(\n  map(() => this.router.getCurrentNavigation() !== null)\n);\n```\n\n到了 Angular 20.2，`getCurrentNavigation()` 已經標記為 Deprecated，改由 `currentNavigation` Signal 取代。\n\n```ts\nimport { computed, inject } from '@angular\u002Fcore';\nimport { Router } from '@angular\u002Frouter';\n\nexport class AppComponent {\n  private readonly router = inject(Router);\n\n  readonly isNavigating = computed(\n    () => this.router.currentNavigation() !== null\n  );\n}\n```\n\n`currentNavigation` 的型別是：\n\n```ts\nSignal\u003CNavigation | null>\n```\n\nNavigation 開始後會取得這次 Navigation 的資訊，結束後則回到 `null`。依賴它的 `computed()` 也會跟著重新計算，不需要再透過 Router Event 觸發讀取。\n\n![getCurrentNavigation() 與 currentNavigation 的差異](https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509050\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Frouter-method-vs-signal.png)\n\n`currentNavigation()` 代表的是正在執行的 Navigation。如果導航已經完成，要讀取目前所在的 URL，則可以使用 `router.url`。\n\n### 實際觀察 currentNavigation()\n\n可以直接把 `currentNavigation()` 的內容顯示在 Template 中：\n\n```html\n\u003Cnav>\n  \u003Ca routerLink=\"\u002Fhome\">Home\u003C\u002Fa>\n  |\n  \u003Ca routerLink=\"\u002Fuser\">User\u003C\u002Fa>\n\u003C\u002Fnav>\n\n\u003Chr \u002F>\n\n\u003Cp>\n  Navigation ID：\n  {{ router.currentNavigation()?.id ?? '目前沒有導航' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  前往：\n  {{ router.currentNavigation()?.initialUrl?.toString() ?? '-' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  Trigger：\n  {{ router.currentNavigation()?.trigger ?? '-' }}\n\u003C\u002Fp>\n\n\u003Cp>\n  目前 URL：\n  {{ router.url }}\n\u003C\u002Fp>\n\n\u003Crouter-outlet \u002F>\n```\n\nComponent 中只需要取得 Router：\n\n```ts\nimport { Component, inject } from '@angular\u002Fcore';\nimport {\n  Router,\n  RouterLink,\n  RouterOutlet,\n} from '@angular\u002Frouter';\n\n@Component({\n  selector: 'app-root',\n  imports: [\n    RouterLink,\n    RouterOutlet,\n  ],\n  templateUrl: '.\u002Fapp.component.html',\n})\nexport class AppComponent {\n  readonly router = inject(Router);\n}\n```\n\n一般 Navigation 很快就會完成，因此畫面上的資料可能只會出現很短的時間。\n\n為了方便觀察，可以加上一個故意延遲兩秒的 Guard：\n\n```ts\nimport { CanActivateFn } from '@angular\u002Frouter';\n\nexport const delayGuard: CanActivateFn = async () => {\n  await new Promise(\n    resolve => setTimeout(resolve, 2000)\n  );\n\n  return true;\n};\n```\n\n接著把 Guard 加到兩個路由：\n\n```ts\nconst routes: Routes = [\n  {\n    path: 'home',\n    component: HomeComponent,\n    canActivate: [delayGuard],\n  },\n  {\n    path: 'user',\n    component: UserComponent,\n    canActivate: [delayGuard],\n  },\n];\n```\n\n這樣在 `\u002Fhome` 與 `\u002Fuser` 之間切換時，就能比較清楚地觀察 Navigation 期間的資料。\n\n例如從 `\u002Fhome` 前往 `\u002Fuser`：\n\n```text\nNavigation ID：11\n前往：\u002Fuser\nTrigger：imperative\n目前 URL：\u002Fhome\n```\n\n此時 Navigation 還沒有完成，所以 `currentNavigation()` 已經記錄這次要前往 `\u002Fuser`，但 `router.url` 仍然停留在原本的 `\u002Fhome`。\n\n其中顯示的資訊說明如下：\n\n- `id`：這次 Navigation 的流水號\n- `initialUrl`：Navigation 一開始要前往的 URL\n- `trigger`：Navigation 的觸發方式，例如 `imperative` 代表透過 `routerLink` 或 `router.navigate()` 主動導航；使用瀏覽器上一頁、下一頁時則可能是 `popstate`\n\n![一般導航與加入 Guard 後的 currentNavigation() 變化](https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790511833\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fcurrent-navigation-observation.png)\n\n> 這部分可以參考 [StackBlitz 範例](https:\u002F\u002Fstackblitz.com\u002Fedit\u002Fldbybozv-tcz79wqj?file=src%2Fmain.ts)\n\n等導航完成後，`currentNavigation()` 會回到 `null`，而 `router.url` 則更新成新的 URL：\n\n```ts\nthis.router.currentNavigation();\n\u002F\u002F null\n\nthis.router.url;\n\u002F\u002F '\u002Fuser'\n```\n\n### 用 currentNavigation() 判斷頁面切換\n\n因為 `currentNavigation()` 只在 Navigation 期間有值，所以也可以直接拿來判斷頁面是否正在切換：\n\n```ts\nreadonly isNavigating = computed(\n  () => this.router.currentNavigation() !== null\n);\n```\n\nTemplate 再根據這個狀態決定是否顯示 Loading：\n\n```ts\n@if (isNavigating()) {\n  \u003Capp-page-loading \u002F>\n}\n\n\u003Crouter-outlet \u002F>\n```\n\n這裡的 Loading 代表的是「頁面正在切換」，適合放在 Layout 或 App Shell 層級，和頁面內某支 API 的 Loading 是不同的狀態。\n\n## lastSuccessfulNavigation\n\n除了正在進行的 Navigation，Router 也提供 `lastSuccessfulNavigation`，用來取得最近一次成功完成的 Navigation。\n\n在 Angular 20 中，`lastSuccessfulNavigation` 還是一般的 `Navigation | null`；到了 Angular 21，則改成：\n\n```ts\nSignal\u003CNavigation | null>\n```\n\n例如：\n\n```ts\nprivate readonly router = inject(Router);\n\nreadonly lastNavigation =\n  this.router.lastSuccessfulNavigation;\n```\n\n讀取時直接呼叫 Signal：\n\n```ts\nconst navigation =\n  this.router.lastSuccessfulNavigation();\n```\n\n它和 `currentNavigation()` 關注的時間點不同：\n\n- `currentNavigation()`：正在執行的 Navigation，結束後會回到 `null`\n- `lastSuccessfulNavigation()`：最近一次成功完成的 Navigation，會保留到下一次成功導航完成\n\n以從 `\u002Fusers` 前往 `\u002Forders` 為例：\n\n![currentNavigation 與 lastSuccessfulNavigation 在導航前後的值](https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790509047\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fnavigation-signals-timeline.png)\n\n## isActive()\n\n除了 Navigation 本身，Angular Router 也提供 Signal-based 的 `isActive()`，用來判斷指定 URL 是否符合目前路由。\n\n例如側邊選單希望 `\u002Fusers` 底下的頁面都維持 Active：\n\n```ts\nimport { inject } from '@angular\u002Fcore';\nimport { isActive, Router } from '@angular\u002Frouter';\n\nexport class SidebarComponent {\n  private readonly router = inject(Router);\n\n  readonly isUsersActive = isActive(\n    '\u002Fusers',\n    this.router\n  );\n}\n```\n\n`isActive()` 回傳：\n\n```ts\nSignal\u003Cboolean>\n```\n\nTemplate 可以直接使用：\n\n```html\n\u003Ca\n  routerLink=\"\u002Fusers\"\n  [class.active]=\"isUsersActive()\"\n>\n  使用者\n\u003C\u002Fa>\n```\n\n沒有傳入比對選項時，`isActive()` 的 `paths` 預設是 `'subset'`，因此 `\u002Fusers\u002F1`、`\u002Fusers\u002F2` 這類子路由也會算是 Active。\n\n其他預設值則是：\n\n```ts\n{\n  paths: 'subset',\n  queryParams: 'subset',\n  matrixParams: 'ignored',\n  fragment: 'ignored',\n}\n```\n\n如果要自己控制比對方式，可以傳入第三個參數：\n\n```ts\nreadonly isUsersActive = isActive(\n  '\u002Fusers',\n  this.router,\n  {\n    paths: 'subset',\n    queryParams: 'ignored',\n    matrixParams: 'ignored',\n    fragment: 'ignored',\n  }\n);\n```\n\n這幾個選項分別控制 URL 不同部分的比對方式：\n\n- `paths: 'subset'`：目前 URL 位於 `\u002Fusers` 底下就算符合，例如 `\u002Fusers`、`\u002Fusers\u002F1`、`\u002Fusers\u002F1\u002Fedit`；改成 `'exact'` 則需要 Path 完全一致\n- `queryParams: 'ignored'`：忽略 Query Params，因此 `\u002Fusers?page=1` 與 `\u002Fusers?page=2` 不會影響 Active 結果\n- `matrixParams: 'ignored'`：忽略 Matrix Params，例如 `\u002Fusers;type=admin` 中的 `type=admin`\n- `fragment: 'ignored'`：忽略 Fragment，因此 `\u002Fusers#profile` 仍然會視為 `\u002Fusers`\n\n過去 Router 也有：\n\n```ts\nthis.router.isActive(...)\n```\n\n但它回傳的是一般的 `boolean`，只代表執行當下的結果。\n\n新的 `isActive()` 則回傳 `Signal\u003Cboolean>`，Router State 改變時會重新計算，從 Angular 21.1 起列為正式 API。\n\n### isActive() 與 RouterLinkActive\n\n如果只是替導覽連結加上 Active 樣式，原本的 `RouterLinkActive` 就已經足夠：\n\n```html\n\u003Ca\n  routerLink=\"\u002Fusers\"\n  routerLinkActive=\"active\"\n  ariaCurrentWhenActive=\"page\"\n>\n  使用者\n\u003C\u002Fa>\n```\n\n`RouterLinkActive` 是 Directive，適合直接在 Template 上處理 class 與 `aria-current`。\n\n`isActive()` 則回傳 Signal，因此 Active 結果還可以在 TypeScript 中繼續參與其他狀態推導。\n\n例如根據目前是否位於 `\u002Fusers` 底下，決定區塊標題：\n\n```ts\nreadonly sectionTitle = computed(() =>\n  this.isUsersActive() ? '使用者管理' : '總覽'\n);\n```\n\n當路由位於 `\u002Fusers` 或它的子路由時，`sectionTitle()` 會得到 `使用者管理`；切換到其他路由後則會重新計算。\n\n![isActive() 的 subset 比對，以及與 RouterLinkActive 的選擇](https:\u002F\u002Fres.cloudinary.com\u002Fdseg0uwc9\u002Fimage\u002Fupload\u002Fv1790510392\u002F2026%20%E9%90%B5%E4%BA%BA%E8%B3%BD\u002FDay%2013%EF%BC%9AcurrentNavigation\u002Fis-active-guide.png)\n\n單純處理導覽樣式時，使用 `RouterLinkActive` 會比較直接；如果 Active 結果還要參與其他程式邏輯，再使用 `isActive()`。\n\n## Router Event 與 Router State\n\nAngular Router 現在可以從「事件」與「狀態」兩個方向處理導航資訊：\n\n- `Router.events`：處理 `NavigationStart`、`NavigationEnd`、`NavigationError` 等導航事件\n- `currentNavigation()`：取得正在進行的 Navigation\n- `lastSuccessfulNavigation()`：取得最近一次成功完成的 Navigation\n- `isActive()`：判斷指定 URL 是否符合目前路由\n\n如果需要知道某件事情「什麼時候發生」，例如記錄頁面瀏覽、追蹤 Navigation Error，使用 `Router.events` 會比較適合。\n\n如果需要的是「目前是什麼狀態」，例如頁面是否正在切換、目前是否位於 `\u002Fusers` 底下，就可以直接從對應的 Signal API 取得。\n\n## 本日結語\n\nRouter 開始提供 Signal API 後，改變的不只是 API 寫法，而是部分 Router State 本身變成了可以直接追蹤的響應式狀態。\n\n`currentNavigation()` 代表正在進行的 Navigation，`lastSuccessfulNavigation()` 保留最近一次成功完成的 Navigation，而 `isActive()` 則讓 URL 比對結果也能直接作為 Signal 使用。\n\n`Router.events` 仍然負責事件發生的時間點；Signal API 則負責讀取 Router 當下的狀態。理解這兩者的差別後，就可以依照實際需求選擇適合的方式，而不需要把所有路由狀態都從 Event Stream 中重新整理一次。\n\n### 資料來源\n\n- [Angular Router 官方文件：Router Lifecycle and Events](https:\u002F\u002Fangular.dev\u002Fguide\u002Frouting\u002Flifecycle-and-events)：Router Event 的種類、`withDebugTracing()`，以及用 `currentNavigation()` 做 Loading 的範例。\n- [Angular Router 官方文件：Read route state](https:\u002F\u002Fangular.dev\u002Fguide\u002Frouting\u002Fread-route-state)：`RouterLinkActive` 與 `isActive()` 的用法。\n- [Angular 原始碼：router.ts](https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Frouter.ts)：`currentNavigation`、`lastSuccessfulNavigation` 的型別，以及 `getCurrentNavigation()` 自 20.2 起 Deprecated。\n- [Angular 原始碼：url\\_tree.ts](https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Furl_tree.ts)：`isActive()` 的比對預設值與實作。\n- [Angular 原始碼：router\\_link\\_active.ts](https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Fblob\u002Fmain\u002Fpackages\u002Frouter\u002Fsrc\u002Fdirectives\u002Frouter_link_active.ts)：`RouterLinkActive` 內部透過 `isActive()` 判斷路由狀態。\n",{"title":206,"description":207},{"name":20,"url":21,"day":214},{"loc":205,"images":3860,"lastmod":208},[3861,3862,3863,3864,3865],{"loc":1268},{"loc":2101},{"loc":2757},{"loc":3076},{"loc":3685},"ithome-2026-day-13","2026-09-27-ithome-2026-day-13",[11,12,13],"p8fofvbzj-c7P_mjCrcjgsI-MxJ7-sJz_55Qm42tyJ4",1792575176606]