Wenn wir Apps mit Status erstellen, ist der Einstiegspunkt der Schlüssel zum Initialisieren unseres Status für unsere Komponenten. Manchmal müssen wir jedoch den Anwendungsstatus innerhalb der URL beibehalten, damit Benutzer Lesezeichen setzen können oder teilen Sie bestimmte Anwendungszustände mit dem Ziel, die Benutzererfahrung zu verbessern und die Navigation zu vereinfachen.
In den meisten Fällen kombinieren wir den Angular Router und ActivatedRoute in unseren Komponenten, um diese Fälle zu lösen, und delegieren diese Verantwortung an die Komponenten oder in anderen Fällen stellen wir eine Mischung aus Komponenten und dem Effekt her, um zu versuchen, das Problem zu lösen.
Ich setze meinen Urlaub auf Menorca fort und habe mir heute Morgen die Zeit genommen, um zu lernen und zu üben, wie man mit dem Zustand im Angular Router umgeht und wie der NGRX-Router dazu beitragen kann, meinen Code zu verbessern und die Verantwortung in meinen Komponenten zu reduzieren.
Ich möchte eine Bearbeitungsseite erstellen, auf der Benutzer die Details eines ausgewählten Ortes ändern, die URL teilen und später zum gleichen Status zurückkehren können. Beispiel: http://localhost/places/2, wobei 2 die ID des bearbeiteten Ortes ist. Benutzer sollten auch in der Lage sein, nach dem Ausführen einer Aktion zur Startseite zurückzukehren.
?Dieser Artikel ist Teil meiner Serie zum Erlernen von NgRx. Wenn Sie mitmachen möchten, schauen Sie es sich bitte an.
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
Klonen Sie das Repo start-with-ngrx, bringen Sie dieses Projekt mit ngrx und der Anwendung bereit und wechseln Sie zum Zweig crud-ngrx
https://github.com/danywalls/start-with-ngrx.git git checkout crud-ngrx
Es ist Zeit zum Codieren!
Öffnen Sie zunächst das Terminal und generieren Sie mithilfe der Angular-CLI eine neue Komponente:
ng g c pages/place-edit
Als nächstes öffnen Sie app.routes.ts und registrieren Sie die PlaceEditComponent mit dem Parameter /places/:id:
{ path: 'places/:id', component: PlaceEditComponent, },
Meine erste Lösung ist eine Kombination aus Dienst, Effekt, Router und aktivierter Route. Es muss an mehreren Stellen Logik hinzugefügt werden.
Methode im Ortsdienst hinzufügen.
Aktionen anhören
Legen Sie den Erfolg fest, um den Status des ausgewählten Ortes zu aktualisieren.
Lesen Sie den ausgewählten Ort in edit-place.component.
Fügen Sie zunächst die Methode getById in der Datei „places.service.ts“ hinzu. Sie ruft den Ort mithilfe der ID ab.
getById(id: string): Observable<Place> { return this.http.get<Place>(`${environment.menorcaPlacesAPI}/${id}`); }
Fügen Sie als Nächstes neue Aktionen zur Verarbeitung von getById hinzu, öffnen Sie „places.actions.ts“ und fügen Sie die Aktionen zum Bearbeiten, Erfolg und Misserfolg hinzu:
// PlacePageActions 'Edit Place': props<{ id: string }>(), // PlacesApiActions 'Get Place Success': props<{ place: Place }>(), 'Get Place Failure': props<{ message: string }>(),
Aktualisieren Sie den Reduzierer, um diese Aktionen zu verarbeiten:
on(PlacesApiActions.getPlaceSuccess, (state, { place }) => ({ ...state, loading: false, placeSelected: place, })), on(PlacesApiActions.getPlaceFailure, (state, { message }) => ({ ...state, loading: false, message, })),
Öffnen Sie place.effects.ts, fügen Sie einen neuen Effekt hinzu, der auf die Aktion „editPlace“ wartet, rufen Sie „placesService.getById“ auf und rufen Sie dann die Antwort ab, um die Aktion „getPlaceSuccess“ auszulösen.
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 } );
Diese Lösung scheint vielversprechend. Ich muss die Aktion „editPlace“ auslösen und den Router in place-card.component.ts einfügen, um zur Route /places:id zu navigieren.
goEditPlace(id: string) { this.store.dispatch(PlacesPageActions.editPlace({ id: this.place().id })); this.router.navigate(['/places', id]); }
Es funktioniert! Aber es gibt einige Nebenwirkungen. Wenn Sie einen anderen Ort auswählen und zur Seite zurückkehren, wird die Auswahl möglicherweise nicht aktualisiert und Sie laden möglicherweise die vorherige. Außerdem erhalten Sie bei langsamen Verbindungen möglicherweise die Fehlermeldung „Nicht gefunden“, da die Datei noch geladen wird.
?Eine Lösung besteht, dank Jörgen de Groot, darin, den Router entsprechend zu versetzen. Öffnen Sie die Datei „places.effect.ts“ und fügen Sie den Dienst und den Router ein. Warten Sie auf die Aktion „editPlace“, rufen Sie die Daten ab, navigieren Sie dann und lösen Sie die Aktion aus.
Der endgültige Code sieht so aus:
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 } );
Jetzt haben wir das Problem behoben, dass die Navigation nur dann erfolgt, wenn der Benutzer auf die Liste der Orte klickt, aber beim Neuladen der Seite nicht funktioniert, weil unser Status in der neuen Route nicht bereit ist, aber wir haben die Möglichkeit, den Effekt zu nutzen Lebenszyklus-Hooks.
Die Effects-Lifecycle-Hooks ermöglichen es uns, Aktionen auszulösen, wenn die Effekte registriert sind, also möchte ich die Aktion „loadPlaces“ auslösen und den Status bereit haben.
export const initPlacesState$ = createEffect( (actions$ = inject(Actions)) => { return actions$.pipe( ofType(ROOT_EFFECTS_INIT), map((action) => PlacesPageActions.loadPlaces()) ); }, { functional: true } );
Lesen Sie mehr über den Effektlebenszyklus und ROOT_EFFECTS_INIT
Okay, ich habe den Status bereit, aber ich habe immer noch ein Problem beim Abrufen der ID aus dem URL-Status.
Eine schnelle Lösung besteht darin, die aktivierte Route in ngOnInit zu lesen. Wenn die ID vorhanden ist, lösen Sie die Aktion editPlace aus. Dadurch wird der Status „selectedPlace“ umgeleitet und festgelegt.
Injizieren Sie also ActivatedRoute erneut in die PlaceEditComponent und implementieren Sie die Logik in ngOnInit.
Der Code sieht so aus:
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.
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.
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
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.
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
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.
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
Das obige ist der detaillierte Inhalt vonAngular Router-URL-Parameter mit NgRx Router Store. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!