Maison >interface Web >js tutoriel >Paramètres d'URL du routeur angulaire utilisant NgRx Router Store

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

WBOY
WBOYoriginal
2024-08-08 15:34:39843parcourir

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 partager des états d'application spécifiques, dans le but d'améliorer l'expérience utilisateur et de faciliter la navigation.

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 en faisant 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, veuillez le consulter.

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

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

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

{
  path: 'places/:id',
  component: PlaceEditComponent,
},

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 faire ajouter de la logique à plusieurs endroits.

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

  • Écouter les actions

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

  • lire 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<Place> {
  return this.http.get<Place>(`${environment.menorcaPlacesAPI}/${id}`);
}

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 }>(),

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,
})),

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 }
);

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]);
}

Ç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 }
);

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 crochets 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 }
);

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 activéRoute 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 }));
    }
  }
}

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

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!

Déclaration:
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
Article précédent:Fonctions JavaScriptArticle suivant:Fonctions JavaScript