Maison> interface Web> js tutoriel> le corps du texte

Paramètres d'URL du routeur angulaire utilisant NgRx Router Store

WBOY
Libérer: 2024-08-08 15:34:39
original
537 Les gens l'ont consulté

Angular Router URL Parameters Using NgRx Router Store

Lorsque nous construisons des applications avec un état, le point d'entrée est essentiel pour initialiser notre état pour nos composants, mais parfois, nous avons des exigences pour préserver l'état de l'application dans l'URL afin de permettre aux utilisateurs de mettre en signet ou de partager des états d'application spécifiques, avec le objectif d'améliorer l'expérience utilisateur et de faciliter la navigation.

Dans la plupart des cas, nous combinons Angular Router et ActivatedRoute dans nos composants pour résoudre ces cas et déléguons cette responsabilité aux composants ou dans d'autres cas, nous faisons un mélange entre les composants et l'effet pour essayer de le résoudre.

Je continue mes vacances à Minorque, j'ai donc pris ce matin pour apprendre et pratiquer comment gérer l'état dans le routeur angulaire et comment le routeur ngrx peut m'aider à améliorer mon code et à réduire la responsabilité dans mes composants.

Scénario

Je souhaite créer une page d'édition où les utilisateurs peuvent modifier les détails d'un lieu sélectionné, partager l'URL et revenir au même état plus tard. Par exemple, http://localhost/places/2, où 2 est l'ID du lieu en cours de modification. Les utilisateurs devraient également pouvoir revenir à la page d'accueil après avoir effectué une action.

?Cet article fait partie de ma série sur l'apprentissage de NgRx. Si vous souhaitez suivre, n'hésitez pas à y jeter un œil.

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

Clonez le repo start-with-ngrx, ce projet apporte avec ngrx et l'application prête et passez à la branche crud-ngrx

https://github.com/danywalls/start-with-ngrx.git git checkout crud-ngrx
Copier après la connexion

Il est temps de coder !

La page d'édition

Ouvrez d'abord le terminal et à l'aide de la CLI Angular, générez un nouveau composant :

ng g c pages/place-edit
Copier après la connexion

Ensuite, ouvrez app.routes.ts et enregistrez le PlaceEditComponent avec le paramètre /places/:id:

{ path: 'places/:id', component: PlaceEditComponent, },
Copier après la connexion

Obtenez l'endroit pour modifier

Ma première solution est une combinaison du service, de l'effet, du routeur et de l'itinéraire activé. Il faudra ajouter de la logique à plusieurs endroits.

  • Ajouter une méthode dans le service des lieux.

  • Écoutez les actions

  • définissez le succès pour mettre à jour l'état du lieu sélectionné.

  • lisez le lieu sélectionné dans edit-place.component.

Tout d'abord, ajoutez la méthode getById dans places.service.ts, elle obtient le lieu en utilisant l'identifiant.

getById(id: string): Observable { return this.http.get(`${environment.menorcaPlacesAPI}/${id}`); }
Copier après la connexion

Ensuite, ajoutez de nouvelles actions pour gérer le getById, ouvrez places.actions.ts et ajoutez les actions à modifier, succès et échec :

// PlacePageActions 'Edit Place': props<{ id: string }>(), // PlacesApiActions 'Get Place Success': props<{ place: Place }>(), 'Get Place Failure': props<{ message: string }>(),
Copier après la connexion

Mettez à jour le réducteur pour gérer ces actions :

on(PlacesApiActions.getPlaceSuccess, (state, { place }) => ({ ...state, loading: false, placeSelected: place, })), on(PlacesApiActions.getPlaceFailure, (state, { message }) => ({ ...state, loading: false, message, })),
Copier après la connexion

Ouvrez place.effects.ts, ajoutez un nouvel effet pour écouter l'action editPlace, appelez placesService.getById, puis obtenez la réponse pour envoyer l'action 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 } );
Copier après la connexion

Cette solution semble prometteuse. Je dois envoyer l'action editPlace et injecter le routeur dans place-card.component.ts pour accéder à la route /places:id.

goEditPlace(id: string) { this.store.dispatch(PlacesPageActions.editPlace({ id: this.place().id })); this.router.navigate(['/places', id]); }
Copier après la connexion

Ça marche ! Mais il y a quelques effets secondaires. Si vous sélectionnez un autre lieu et revenez à la page, la sélection risque de ne pas être mise à jour et vous risquez de charger la précédente. De plus, avec des connexions lentes, vous pourriez obtenir une erreur « introuvable » car le chargement est toujours en cours.

?Une solution, grâce à Jörgen de Groot, consiste à déplacer le routeur vers l'effet. Ouvrez le fichier places.effect.ts et injectez le service et le routeur. Écoutez l'action editPlace, récupérez les données, puis naviguez et envoyez l'action.

Le code final ressemble à ceci :

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 } );
Copier après la connexion

Maintenant, nous avons résolu le problème de la navigation uniquement lorsque l'utilisateur clique dans la liste des lieux, mais lors du rechargement de la page, cela ne fonctionne pas, car notre état n'est pas prêt dans le nouvel itinéraire, mais nous avons la possibilité d'utiliser l'effet hooks de cycle de vie .

Les hooks de cycle de vie des effets nous permettent de déclencher des actions lorsque les effets sont enregistrés, je veux donc déclencher l'action loadPlaces et avoir l'état prêt.

export const initPlacesState$ = createEffect( (actions$ = inject(Actions)) => { return actions$.pipe( ofType(ROOT_EFFECTS_INIT), map((action) => PlacesPageActions.loadPlaces()) ); }, { functional: true } );
Copier après la connexion

En savoir plus sur le cycle de vie des effets et ROOT_EFFECTS_INIT

D'accord, j'ai l'état prêt, mais j'ai toujours un problème lors de l'obtention de l'ID à partir de l'état de l'URL.

Une solution rapide consiste à lire l'activateRoute dans ngOnInit. Si l'identifiant est présent, distribuez l'action editPlace. Cela redirigera et définira l'état selectedPlace.

Alors, injectez à nouveau activateRoute dans le PlaceEditComponent et implémentez la logique dans ngOnInit.

Le code ressemble à ceci :

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 })); } } }
Copier après la connexion

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.

Copier après la connexion

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']); } }
Copier après la connexion

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()
Copier après la connexion

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
Copier après la connexion

Next, open app.config and register routerReducer and provideRouterStore.

providers: [ ..., provideStore({ router: routerReducer, home: homeReducer, places: placesReducer, }), ... provideRouterStore(), ],
Copier après la connexion

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, };
Copier après la connexion

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); }
Copier après la connexion

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(), }, });
Copier après la connexion

Update the cancel method in the PlacesComponent and dispatch the cancelPlace action.

cancel() { this.#store.dispatch(PlacesPageActions.cancelPlace()); }
Copier après la connexion

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, }, );
Copier après la connexion

Finally, the last step is to update the place-card to dispatch the selectPlace action and use a routerLink.

Edit
Copier après la connexion

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!