首頁 >web前端 >js教程 >使用 NgRx 路由器儲存的 Angular 路由器 URL 參數

使用 NgRx 路由器儲存的 Angular 路由器 URL 參數

WBOY
WBOY原創
2024-08-08 15:34:39845瀏覽

Angular Router URL Parameters Using NgRx Router Store

當我們建立具有狀態的應用程式時,入口點是初始化元件狀態的關鍵,但有時,我們需要在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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:JavaScript 函數下一篇:JavaScript 函數