search
HomeWeb Front-endJS TutorialAngular Router URL Parameters Using NgRx Router Store

Angular Router URL Parameters Using NgRx Router Store

When we build apps with state, the entry point is key to initialize our state for our components, but sometimes, we have requirements to preserve application state within the URL to allow users to bookmark or share specific application states, with the goal of improves user experience and make easy the navigation.

Most of case, we combine the Angular Router and ActivatedRoute in our components to solve these cases and delegate this responsibility to the components or in others cases making a mix between components and the effect to try solve it.

I'm continuing my holidays in Menorca, so I took this morning to learn and practice how to handle state in the Angular Router and how the ngrx router can help improve my code and reduce the responsibility in my components.

Scenario

I want to create an edit page where users can modify the details of a selected place, share the URL, and return to the same state later. For example, http://localhost/places/2, where 2 is the ID of the place being edited. Users should also be able to return to the home page after performing an action.

?This article is part of my series on learning NgRx. If you want to follow along, please check it out.

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

Clone the repo start-with-ngrx, this project bring with ngrx and the application ready and switch to the branch crud-ngrx

https://github.com/danywalls/start-with-ngrx.git
git checkout crud-ngrx

It's time to coding!

The Edit Page

First open terminal and using the Angular CLI, generate a new component:

ng g c pages/place-edit

Next, open app.routes.ts and register the PlaceEditComponent with the parameter /places/:id:

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

Get The Place To Edit

My first solution is a combination of the service, effect, router and activated route. It will require make add logic in several places.

  • Add method in the places service.

  • Listen actions

  • set the success to update the state of the selected place.

  • read the selected place in edit-place.component.

First, add getById method in the places.service.ts, it get the place by using the id.

getById(id: string): Observable<place> {
  return this.http.get<place>(`${environment.menorcaPlacesAPI}/${id}`);
}
</place></place>

Next, add new actions to handle the getById, open places.actions.ts add the actions to edit, success and failure:

// PlacePageActions
'Edit Place': props(),

// PlacesApiActions
'Get Place Success': props(),
'Get Place Failure': props(),

Update the reducer to handle these actions:

on(PlacesApiActions.getPlaceSuccess, (state, { place }) => ({
  ...state,
  loading: false,
  placeSelected: place,
})),
on(PlacesApiActions.getPlaceFailure, (state, { message }) => ({
  ...state,
  loading: false,
  message,
})),

Open place.effects.ts, add a new effect to listen for the editPlace action, call placesService.getById, and then get the response to dispatch the getPlaceSuccess action.

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

This solution seems promising. I need to dispatch the editPlace action and inject the router in place-card.component.ts to navigate to the /places:id route.

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

It works! But there are some side effects. If you select another place and go back to the page, the selection might not be updated, and you may load the previous one. Also, with slow connections, you might get a "not found" error because it is still loading.

?One solution, thanks to Jörgen de Groot, is to move the router to the effect. Open the places.effect.ts file and inject the service and router. Listen for the editPlace action, get the data, then navigate and dispatch the action.

The final code looks like this:

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

Now we fixed the issue of navigating only when the user click in the list of places, but when reloading the page that it's not working, because our state is not ready in the new route, but we have an option use the effect lifecycle hooks.

The effects lifecycle hooks allow us to trigger actions when the effects are register, so I wan trigger the action loadPlaces and have the state ready.

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

Read more about Effect lifecycle and ROOT_EFFECTS_INIT

Okay, I have the state ready, but I'm still having an issue when getting the ID from the URL state.

A quick fix is to read the activatedRoute in ngOnInit. If the id is present, dispatch the action editPlace. This will redirect and set the selectedPlace state.

So, inject activatedRoute again in the PlaceEditComponent and implement the logic in ngOnInit.

The code looks like this:

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 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(),
    'Update Place': props(),
    'Delete Place': props(),
    'Cancel Place': emptyProps(),
    'Select Place': props(),
    '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 place 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

The above is the detailed content of Angular Router URL Parameters Using NgRx Router Store. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Javascript Data Types : Is there any difference between Browser and NodeJs?Javascript Data Types : Is there any difference between Browser and NodeJs?May 14, 2025 am 12:15 AM

JavaScript core data types are consistent in browsers and Node.js, but are handled differently from the extra types. 1) The global object is window in the browser and global in Node.js. 2) Node.js' unique Buffer object, used to process binary data. 3) There are also differences in performance and time processing, and the code needs to be adjusted according to the environment.

JavaScript Comments: A Guide to Using // and /* */JavaScript Comments: A Guide to Using // and /* */May 13, 2025 pm 03:49 PM

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

Python vs. JavaScript: A Comparative Analysis for DevelopersPython vs. JavaScript: A Comparative Analysis for DevelopersMay 09, 2025 am 12:22 AM

The main difference between Python and JavaScript is the type system and application scenarios. 1. Python uses dynamic types, suitable for scientific computing and data analysis. 2. JavaScript adopts weak types and is widely used in front-end and full-stack development. The two have their own advantages in asynchronous programming and performance optimization, and should be decided according to project requirements when choosing.

Python vs. JavaScript: Choosing the Right Tool for the JobPython vs. JavaScript: Choosing the Right Tool for the JobMay 08, 2025 am 12:10 AM

Whether to choose Python or JavaScript depends on the project type: 1) Choose Python for data science and automation tasks; 2) Choose JavaScript for front-end and full-stack development. Python is favored for its powerful library in data processing and automation, while JavaScript is indispensable for its advantages in web interaction and full-stack development.

Python and JavaScript: Understanding the Strengths of EachPython and JavaScript: Understanding the Strengths of EachMay 06, 2025 am 12:15 AM

Python and JavaScript each have their own advantages, and the choice depends on project needs and personal preferences. 1. Python is easy to learn, with concise syntax, suitable for data science and back-end development, but has a slow execution speed. 2. JavaScript is everywhere in front-end development and has strong asynchronous programming capabilities. Node.js makes it suitable for full-stack development, but the syntax may be complex and error-prone.

JavaScript's Core: Is It Built on C or C  ?JavaScript's Core: Is It Built on C or C ?May 05, 2025 am 12:07 AM

JavaScriptisnotbuiltonCorC ;it'saninterpretedlanguagethatrunsonenginesoftenwritteninC .1)JavaScriptwasdesignedasalightweight,interpretedlanguageforwebbrowsers.2)EnginesevolvedfromsimpleinterpreterstoJITcompilers,typicallyinC ,improvingperformance.

JavaScript Applications: From Front-End to Back-EndJavaScript Applications: From Front-End to Back-EndMay 04, 2025 am 12:12 AM

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Python vs. JavaScript: Which Language Should You Learn?Python vs. JavaScript: Which Language Should You Learn?May 03, 2025 am 12:10 AM

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

WebStorm Mac version

WebStorm Mac version

Useful JavaScript development tools

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

SublimeText3 Linux new version

SublimeText3 Linux new version

SublimeText3 Linux latest version

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools