使用 NgRx 路由器儲存的 Angular 路由器 URL 參數
當我們建立具有狀態的應用程式時,入口點是初始化元件狀態的關鍵,但有時,我們需要在URL 中保留應用程式狀態以允許用戶添加書籤或分享特定的應用程式狀態,目的是改善用戶體驗並簡化導航。
大多數情況下,我們在組件中結合 Angular Router 和 ActivatedRoute 來解決這些情況,並將此責任委託給組件,或者在其他情況下,在組件和效果之間進行混合來嘗試解決它。
我將繼續在梅諾卡島度假,所以今天早上我學習和練習瞭如何處理 Angular 路由器中的狀態,以及 ngrx 路由器如何幫助改進我的程式碼並減少組件中的責任。
設想
我想建立一個編輯頁面,使用者可以在其中修改所選地點的詳細資訊、共享 URL,並稍後返回相同的狀態。例如,http://localhost/places/2,其中 2 是正在編輯的地點的 ID。用戶還應該能夠在執行操作後返回主頁。
?本文是我學習 NgRx 系列的一部分。如果你想跟的話,請看一下。
https://www.danywalls.com/understanding-when-and-why-to-implement-ngrx-in-angular
https://www.danywalls.com/how-to-debug-ngrx-using-redux-devtools
https://www.danywalls.com/how-to-implement-actioncreationgroup-in-ngrx
https://www.danywalls.com/how-to-use-ngrx-selectors-in-angular
https://danywalls.com/when-to-use-concatmap-mergemap-switchmap-and-exhaustmap-operators-in-building-a-crud-with-ngrx
複製 repo start-with-ngrx,該專案帶有 ngrx 和準備好的應用程序,並切換到分支 crud-ngrx
https://github.com/danywalls/start-with-ngrx.git git checkout crud-ngrx
編碼時間到了!
編輯頁面
先開啟終端機並使用 Angular CLI,產生一個新元件:
ng g c pages/place-edit
接下來,開啟 app.routes.ts 並使用參數 /places/:id:
註冊 PlaceEditComponent
{ path: 'places/:id', component: PlaceEditComponent, },
取得編輯位置
我的第一個解決方案是服務、效果、路由器和啟動路由的組合。它將需要在多個地方添加邏輯。
在地點服務中加入方法。
聆聽動作
設定成功更新所選地點的狀態。
讀取 edit-place.component 中選定的位置。
首先,在places.service.ts中加入getById方法,它透過id取得地點。
getById(id: string): Observable<Place> { return this.http.get<Place>(`${environment.menorcaPlacesAPI}/${id}`); }
接下來,新增新的動作來處理getById,開啟places.actions.ts加入要編輯的動作,成功與失敗:
// PlacePageActions 'Edit Place': props<{ id: string }>(), // PlacesApiActions 'Get Place Success': props<{ place: Place }>(), 'Get Place Failure': props<{ message: string }>(),
更新減速器來處理這些操作:
on(PlacesApiActions.getPlaceSuccess, (state, { place }) => ({ ...state, loading: false, placeSelected: place, })), on(PlacesApiActions.getPlaceFailure, (state, { message }) => ({ ...state, loading: false, message, })),
開啟 place.effects.ts,加入新效果監聽 editPlace 操作,呼叫placesService.getById,然後取得回應以調度 getPlaceSuccess 操作。
export const getPlaceEffect$ = createEffect( (actions$ = inject(Actions), placesService = inject(PlacesService)) => { return actions$.pipe( ofType(PlacesPageActions.editPlace), mergeMap(({ id }) => placesService.getById(id).pipe( map((apiPlace) => PlacesApiActions.getPlaceSuccess({ place: apiPlace }) ), catchError((error) => of(PlacesApiActions.getPlaceFailure({ message: error })) ) ) ) ); }, { functional: true } );
這個解決方案看起來很有前途。我需要調度 editPlace 操作並將路由器注入 place-card.component.ts 以導航到 /places:id 路由。
goEditPlace(id: string) { this.store.dispatch(PlacesPageActions.editPlace({ id: this.place().id })); this.router.navigate(['/places', id]); }
它有效!但也有一些副作用。如果您選擇另一個位置並返回頁面,則選擇可能不會更新,您可以載入上一個位置。此外,如果連線速度較慢,您可能會收到「未找到」錯誤,因為它仍在載入。
?感謝 Jörgen de Groot,一個解決方案是將路由器移至效果器。開啟places.effect.ts 檔案並注入服務和路由器。監聽 editPlace 操作,取得數據,然後導覽並分派該操作。
最終程式碼如下圖所示:
export const getPlaceEffect$ = createEffect( ( actions$ = inject(Actions), placesService = inject(PlacesService), router = inject(Router) ) => { return actions$.pipe( ofType(PlacesPageActions.editPlace), mergeMap(({ id }) => placesService.getById(id).pipe( tap(() => console.log('get by id')), map((apiPlace) => { router.navigate(['/places', apiPlace.id]); return PlacesApiActions.getPlaceSuccess({ place: apiPlace }); }), catchError((error) => of(PlacesApiActions.getPlaceFailure({ message: error })) ) ) ) ); }, { functional: true } );
現在我們解決了僅當用戶單擊地點列表時導航的問題,但在重新加載頁面時它不起作用,因為我們的狀態在新路線中尚未準備好,但我們可以選擇使用效果生命週期掛鉤。
效果生命週期掛鉤允許我們在註冊效果時觸發操作,因此我想觸發操作 loadPlaces 並準備好狀態。
export const initPlacesState$ = createEffect( (actions$ = inject(Actions)) => { return actions$.pipe( ofType(ROOT_EFFECTS_INIT), map((action) => PlacesPageActions.loadPlaces()) ); }, { functional: true } );
了解更多有關 Effect 生命週期和 ROOT_EFFECTS_INIT 的資訊
好的,我已準備好狀態,但從 URL 狀態取得 ID 時仍然遇到問題。
一個快速修復方法是讀取 ngOnInit 中的activatedRoute。如果 id 存在,則分派操作 editPlace。這將重定向並設定 selectedPlace 狀態。
所以,在PlaceEditComponent中再次注入activatedRoute,並在ngOnInit中實現邏輯。
程式碼如下圖:
export class PlaceEditComponent implements OnInit { store = inject(Store); place$ = this.store.select(PlacesSelectors.selectPlaceSelected); activatedRoute = inject(ActivatedRoute); ngOnInit(): void { const id = this.activatedRoute.snapshot.params['id']; if (id) { this.store.dispatch(PlacesPageActions.editPlace({ id })); } } }
It works! Finally, we add a cancel button to redirect to the places route and bind the click event to call a new method, cancel.
<button (click)="cancel()" class="button is-light" type="reset">Cancel</button>
Remember to inject the router to call the navigate method to the places URL. The final code looks like this:
export class PlaceEditComponent implements OnInit { store = inject(Store); place$ = this.store.select(PlacesSelectors.selectPlaceSelected); activatedRoute = inject(ActivatedRoute); router = inject(Router); ngOnInit(): void { const id = this.activatedRoute.snapshot.params['id']; if (id) { this.store.dispatch(PlacesPageActions.editPlace({ id })); } } cancel() { router.navigate(['/places']); } }
Okay, it works with all features, but our component is handling many tasks, like dispatching actions and redirecting navigation. What will happen when we need more features? We can simplify everything by using NgRx Router, which will reduce the amount of code and responsibility in our components.
Why NgRx Router Store ?
The NgRx Router Store makes it easy to connect our state with router events and read data from the router using build'in selectors. Listening to router actions simplifies interaction with the data and effects, keeping our components free from extra dependencies like the router or activated route.
Router Actions
NgRx Router provide five router actions, these actions are trigger in order
ROUTER_REQUEST: when start a navigation.
ROUTER_NAVIGATION: before guards and revolver , it works during navigation.
ROUTER?NAVIGATED: When completed navigation.
ROUTER_CANCEL: when navigation is cancelled.
ROUTER_ERROR: when there is an error.
Read more about ROUTER_ACTIONS
Router Selectors
It helps read information from the router, such as query params, data, title, and more, using a list of built-in selectors provided by the function getRouterSelectors.
export const { selectQueryParam, selectRouteParam} = getRouterSelectors()
Read more about Router Selectors
Because, we have an overview of NgRx Router, so let's start implementing it in the project.
Configure NgRx Router
First, we need to install NgRx Router. It provides selectors to read from the router and combine with other selectors to reduce boilerplate in our components.
In the terminal, install ngrx/router-store using the schematics:
ng add @ngrx/router-store
Next, open app.config and register routerReducer and provideRouterStore.
providers: [ ..., provideStore({ router: routerReducer, home: homeReducer, places: placesReducer, }), ... provideRouterStore(), ],
We have the NgRx Router in our project, so now it's time to work with it!
Read more about install NgRx Router
Simplify using NgRx RouterSelectors
Instead of making an HTTP request, I will use my state because the ngrx init effect always updates my state when the effect is registered. This means I have the latest data. I can combine the selectPlaces selector with selectRouterParams to get the selectPlaceById.
Open the places.selector.ts file, create and export a new selector by combining selectPlaces and selectRouteParams.
The final code looks like this:
export const { selectRouteParams } = getRouterSelectors(); export const selectPlaceById = createSelector( selectPlaces, selectRouteParams, (places, { id }) => places.find((place) => place.id === id), ); export default { placesSelector: selectPlaces, selectPlaceSelected: selectPlaceSelected, loadingSelector: selectLoading, errorSelector: selectError, selectPlaceById, };
Perfect, now it's time to update and reduce all dependencies in the PlaceEditComponent, and use the new selector PlacesSelectors.selectPlaceById. The final code looks like this:
export class PlaceEditComponent { store = inject(Store); place$ = this.store.select(PlacesSelectors.selectPlaceById); }
Okay, but what about the cancel action and redirect? We can dispatch a new action, cancel, to handle this in the effect.
First, open places.action.ts and add the action 'Cancel Place': emptyProps(). the final code looks like this:
export const PlacesPageActions = createActionGroup({ source: 'Places', events: { 'Load Places': emptyProps(), 'Add Place': props<{ place: Place }>(), 'Update Place': props<{ place: Place }>(), 'Delete Place': props<{ id: string }>(), 'Cancel Place': emptyProps(), 'Select Place': props<{ place: Place }>(), 'UnSelect Place': emptyProps(), }, });
Update the cancel method in the PlacesComponent and dispatch the cancelPlace action.
cancel() { this.#store.dispatch(PlacesPageActions.cancelPlace()); }
The final step is to open place.effect.ts, add the returnHomeEffects effect, inject the router, and listen for the cancelPlace action. Use router.navigate to redirect when the action is dispatched.
export const returnHomeEffect$ = createEffect( (actions$ = inject(Actions), router = inject(Router)) => { return actions$.pipe( ofType(PlacesPageActions.cancelPlace), tap(() => router.navigate(['/places'])), ); }, { dispatch: false, functional: true, }, );
Finally, the last step is to update the place-card to dispatch the selectPlace action and use a routerLink.
<a (click)="goEditPlace()" [routerLink]="['/places', place().id]" class="button is-info">Edit</a>
Done! We did it! We removed the router and activated route dependencies, kept the URL parameter in sync, and combined it with router selectors.
Recap
I learned how to manage state using URL parameters with NgRx Router Store in Angular. I also integrated NgRx with Angular Router to handle state and navigation, keeping our components clean. This approach helps manage state better and combines with Router Selectors to easily read router data.
Source Code: https://github.com/danywalls/start-with-ngrx/tree/router-store
Resources: https://ngrx.io/guide/router-store
以上是使用 NgRx 路由器儲存的 Angular 路由器 URL 參數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。
