幾週前發布的Angular v19 標誌著框架內訊號革命的一個重要里程碑,輸入、模型、輸出和訊號查詢 API現已正式升級為穩定版。
但這還不是全部!這個主要版本也引進了旨在進一步推進訊號革命的強大新工具:新的資源API。
顧名思義,這個新的資源 API 旨在透過充分利用訊號的力量來簡化載入非同步資源!
重要提示:在撰寫本文時,新的資源 API 仍處於實驗階段。這意味著它在變得穩定之前可能會發生變化,因此使用它需要您自擔風險。 ?
讓我們深入了解它的工作原理以及它如何簡化非同步資源的處理!
大多數訊號 API 都是同步的,但在實際應用中,處理非同步資源至關重要,例如從伺服器取得資料或即時管理使用者互動。
這就是新的資源 API 發揮作用的地方。
使用資源,您可以輕鬆地透過訊號消耗非同步資源,從而輕鬆管理資料擷取、處理載入狀態,並在相關訊號參數變更時觸發新的擷取。
建立資源更簡單的方法是使用resource()函數:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
此函數接受 ResourceOptions 配置物件作為輸入,允許您指定以下屬性:
借助這些配置,我們可以輕鬆定義非同步依賴項,它將始終被有效使用並保持最新。
一旦建立了資源,就會執行loader函數,然後產生的非同步請求開始:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
每當有訊號表示request 函數依賴於變更時,request 函數會再次運行,如果傳回新參數,則會觸發loader 函數取得更新後的資源值:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
如果沒有提供request 函數,則loader 函數將只運行一次,除非使用reload重新載入Resource方法(更多下文)。
最後,一旦父元件或服務被銷毀,資源也會被銷毀,除非提供了特定的注入器。
在這種情況下,資源將保持活動狀態,並且僅當提供的注入器本身被銷毀時才會被銷毀。
為了優化資料獲取,如果 request() 計算發生變化而先前的值仍在加載,資源 可以中止未完成的請求。
為了管理此問題,loader() 函數提供了 abortSignal,您可以將其傳遞給正在進行的請求,例如 fetch。此請求監聽 abortSignal 並在觸發時取消操作,確保高效的資源管理並防止不必要的網路請求:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
基於此,建議主要針對 GET 請求使用 Resource API,因為它們通常可以安全地取消而不會引起問題。
對於 POST 或 UPDATE 請求,取消可能會導致意想不到的副作用,例如不完整的資料提交或更新。但是,如果您需要針對這些類型的請求提供類似的功能,則可以使用 effect() 方法來安全地管理操作。
資源 API 為其狀態提供了多個訊號屬性,您可以直接在元件或服務中輕鬆使用它們:
以下是如何在元件中使用資源的範例:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
在此範例中,Resource 用於根據 id 訊號的值從 API 取得數據,該訊號可以透過點擊按鈕來遞增。
每當使用者點擊按鈕時,id 訊號值都會發生變化,從而觸發 loader 函數從遠端 API 取得新項目。
由於 Resource API 公開的訊號屬性,UI 會自動使用所取得的資料進行更新。
如前所述,狀態訊號提供有關資源在任何給定時刻的當前狀態的資訊。
status 訊號的可能值由 ResourceStatus 列舉定義。以下是這些狀態及其對應值的摘要:
這些狀態有助於追蹤資源的進度,並有助於更好地處理應用程式中的非同步操作。
鑑於這些狀態的複雜性,資源 API 提供了 hasValue() 方法,該方法根據當前狀態傳回一個布林值。
這確保了有關資源狀態的準確信息,提供了一種更可靠的方法來處理非同步操作,而不依賴於值,該值在某些狀態下可能是未定義。
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
此方法是響應式的,允許您像訊號一樣使用和追蹤它。
Resource API 也提供了 isLoading 訊號,該訊號會傳回資源目前是否處於 Loading 或 Reloading 狀態:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
由於 isLoading 是一個計算訊號,因此可以對其進行反應式跟踪,從而允許您使用訊號 API 即時監控載入狀態。
Resource提供的值訊號是一個WritableSignal,它允許您使用set()和update()手動更新它) 功能:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
注意:如您所見,手動更新訊號的值也會將狀態設為5,這表示「本地”,表示該值是本地設定的。
手動設定的值將持續存在,直到設定新值或執行新請求,這將使用新值覆寫它:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request, abortSignal }) => fetch(RESOURCE_URL + request.id, { signal: abortSignal }) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // Triggers a new request, causing the previous fetch to be aborted // Then the loader function to run again generating a new fetch request id.set(2); console.log(myResource.status()); // Prints: 2 (which means "Loading")
注意: Resource API 的value 訊號使用與新的LinkedSignal API 相同的模式,但不使用它在引擎蓋下。 ?
為了簡化value 訊號的使用,Resource API 為set、update 和updateasReadonly 方法。
asReadonly 方法特別有用,因為它會傳回 value 訊號的唯讀實例,僅允許讀取存取並防止任何意外修改。
您可以使用此方法透過匯出 value:
的唯讀實例來建立管理和追蹤資源值變更的服務
import { Component, resource, signal } from '@angular/core'; const BASE_URL = 'https://jsonplaceholder.typicode.com/todos/'; @Component({ selector: 'my-component', template: ` @if (myResource.value()) { {{ myResource.value().title }} } <button (click)="fetchNext()">Fetch next item</button> ` }) export class MyComponent { private id = signal(1); protected myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(BASE_URL + request.id).then((response) => response.json()), }); protected fetchNext(): void { this.id.update((id) => id + 1); } }
這將防止消費者修改值,降低意外變更的風險,並提高複雜資料管理的一致性。
使用非同步資源時,您可能會遇到需要刷新資料或銷毀資源的情況。
為了處理這些場景,資源 API 提供了兩種專用方法,為管理這些操作提供有效的解決方案。
reload() 方法指示 Resource 重新執行非同步請求,確保它取得最新的資料:
import { resource, signal } from '@angular/core'; const RESOURCE_URL = 'https://jsonplaceholder.typicode.com/todos/'; private id = signal(1); private myResource = resource({ request: () => ({ id: this.id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id), });
如果成功啟動重新載入,reload() 方法將傳回 true。
如果無法執行重新加載,無論是因為沒有必要,例如當狀態已正在加載或正在重新加載,或不支持,例如當狀態為時空閒,該方法回傳false.
destroy() 方法手動銷毀Resource,銷毀任何用於追蹤請求更改的effect(),取消任何待處理的請求,並設定狀態為空閒,同時將數值重設為未定義:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading")
資源被銷毀後,它將不再回應請求更改或reload()操作。
注意:此時,雖然value訊號仍然可寫,但Resource將失去其預期目的,不再發揮其功能,變得無用。 ?
與迄今為止引入的幾乎所有基於訊號的 API 一樣,資源 API 還提供了一個互通性實用程序,用於與 RxJS 無縫整合。
您可以使用rxResource() 方法來建立基於Promise 的Resource,而不是使用resource() 方法來使用可觀察:
import { resource, signal } from "@angular/core"; const RESOURCE_URL = "https://jsonplaceholder.typicode.com/todos/"; const id = signal(1); const myResource = resource({ request: () => ({ id: id() }), loader: ({ request }) => fetch(RESOURCE_URL + request.id) }); console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 1 , ... } id.set(2); // Triggers a request, causing the loader function to run again console.log(myResource.status()); // Prints: 2 (which means "Loading") // After the fetch resolves console.log(myResource.status()); // Prints: 4 (which means "Resolved") console.log(myResource.value()); // Prints: { "id": 2 , ... }
注意: rxResource() 方法實際上是由 rxjs-interop 套件公開的。
由 loader() 函數產生的 Observable 將只考慮第一個發射值,忽略後續發射。
感謝大家在這個美好的2024年跟隨我。 ??
這是充滿挑戰的一年,但也非常有收穫。我對 2025 年有宏偉的計劃,我迫不及待地想開始實施它們。 ?
我想得到您的回饋,所以請留下評論、按讚或追蹤。 ?
然後,如果您真的喜歡它,請在您的社區、技術兄弟和任何您想要的人中分享它。別忘了在 LinkedIn 上追蹤我。 ??
以上是Angular resource() 和 rxResource() API:您需要了解的內容的詳細內容。更多資訊請關注PHP中文網其他相關文章!