Implementeer Geavanceerde State Management in Angular met NgRx
description
Met deze prompt leer je hoe je complexe applicatiestatussen beheert met NgRx in Angular, wat leidt tot beter gestructureerde en schaalbare code. Het helpt je asynchrone processen efficiënt te integreren en voorkomt veelvoorkomende valkuilen, waardoor je applicaties robuuster en onderhoudsvriendelijker worden.
prompt
Help me om geavanceerde state management te implementeren in mijn Angular applicatie met NgRx. Leg uit hoe ik actions, reducers, selectors en effects kan gebruiken om de applicatiestatus effici ...
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hieronder vind je een overzicht van hoe je geavanceerd state management met NgRx kunt implementeren voor een productcatalogus met filters en winkelwagenstatus in Angular:
1. Actions
Gebruik actions om gebruikersinteracties en asynchrone gebeurtenissen te representeren. Bijvoorbeeld:
// Actions voor productcatalogus
import { createAction, props } from '@ngrx/store';
export const loadProducts = createAction('[Product] Load Products');
export const loadProductsSuccess = createAction(
'[Product] Load Products Success',
props<{ products: Product[] }>()
);
export const loadProductsFailure = createAction(
'[Product] Load Products Failure',
props<{ error: any }>()
);
export const setFilter = createAction(
'[Filter] Set Filter',
props<{ filter: string }>()
);
export const addToCart = createAction(
'[Cart] Add Product',
props<{ productId: number }>()
);
export const removeFromCart = createAction(
'[Cart] Remove Product',
props<{ productId: number }>()
);
2. Reducers
Reducers beheren de staat op basis van de acties. Ze zijn puur en voorspelbaar.
import { createReducer, on } from '@ngrx/store';
export interface ProductState {
products: Product[];
loading: boolean;
error: any;
filter: string;
cart: number[]; // array van productId's
}
export const initialState: ProductState = {
products: [],
loading: false,
error: null,
filter: '',
cart: []
};
export const productReducer = createReducer(
initialState,
on(loadProducts, state => ({ ...state, loading: true })),
on(loadProductsSuccess, (state, { products }) => ({
...state,
products,
loading: false
})),
on(loadProductsFailure, (state, { error }) => ({
...state,
error,
loading: false
})),
on(setFilter, (state, { filter }) => ({ ...state, filter })),
on(addToCart, (state, { productId }) => ({
...state,
cart: [...state.cart, productId]
})),
on(removeFromCart, (state, { productId }) => ({
...state,
cart: state.cart.filter(id => id !== productId)
}))
);
3. Selectors
Selectores maken het gemakkelijk om gedeelten van de state te verkrijgen.
import { createSelector, createFeatureSelector } from '@ngrx/store';
export const selectProductState = createFeatureSelector<ProductState>('product');
export const selectAllProducts = createSelector(
selectProductState,
state => state.products
);
export const selectFilteredProducts = createSelector(
selectAllProducts,
selectProductState,
(products, state) => products.filter(p => p.name.includes(state.filter))
);
export const selectCart = createSelector(
selectProductState,
state => state.cart
);
export const selectCartItems = createSelector(
selectAllProducts,
selectCart,
(products, cart) => products.filter(p => cart.includes(p.id))
);
4. Effects
Gebruik Effects voor asynchrone operaties zoals API-aanroepen. Hiermee houd je de componenten schoon en reageer je op acties.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { ProductService } from './product.service';
import { loadProducts, loadProductsSuccess, loadProductsFailure } from './product.actions';
import { mergeMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Injectable()
export class ProductEffects {
loadProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(loadProducts),
mergeMap(() =>
this.productService.getProducts().pipe(
map(products => loadProductsSuccess({ products })),
catchError(error => of(loadProductsFailure({ error })))
)
)
)
);
constructor(private actions$: Actions, private productService: ProductService) {}
}
5. Asynchrone operaties en optimalisatie
- Gebruik Effects om API calls te beheren, zodat componenten niet direct met services werken.
- Implementeer lazy loading en memoization via selectors om onnodige herberekeningen te voorkomen.
- Gebruik OnPush change detection in componenten voor betere prestaties.
- Splits grote state in kleinere feature-staten indien nodig.
- Voeg caching toe indien mogelijk, bijvoorbeeld door opgeslagen API-responses te hergebruiken.
Voor jouw use case betekent dit dat je:
- Een effect hebt dat productgegevens ophaalt bij het laden.
- Actions en reducers die de filterstatus en winkelwagen bijhouden.
- Selectors die gefilterde productlijsten en winkelwagenitems ophalen.
- Componenten die deze state gebruiken en reageren op veranderingen.
Zo creëer je een schaalbare, efficiënte en onderhoudbare state management structuur voor je Angular applicatie met NgRx.
provider: deepseek
model: deepseek-chat
Zeker! Ik help je met geavanceerde state management in Angular met NgRx voor je productcatalogus. Hier is een complete implementatiegids:
## 1. Installatie en Setup
```bash
ng add @ngrx/store@latest
ng add @ngrx/effects@latest
ng add @ngrx/store-devtools@latest
ng add @ngrx/entity@latest
```
## 2. State Structuur
```typescript
// app.state.ts
export interface AppState {
products: ProductsState;
cart: CartState;
filters: FilterState;
}
export interface ProductsState {
products: Product[];
loading: boolean;
error: string | null;
}
export interface CartState {
items: CartItem[];
total: number;
}
export interface FilterState {
category: string | null;
priceRange: { min: number; max: number };
sortBy: 'name' | 'price' | 'rating';
}
```
## 3. Actions
```typescript
// products.actions.ts
import { createAction, props } from '@ngrx/store';
// Product Actions
export const loadProducts = createAction('[Products] Load Products');
export const loadProductsSuccess = createAction(
'[Products] Load Products Success',
props<{ products: Product[] }>()
);
export const loadProductsFailure = createAction(
'[Products] Load Products Failure',
props<{ error: string }>()
);
// Cart Actions
export const addToCart = createAction(
'[Cart] Add Item',
props<{ product: Product; quantity: number }>()
);
export const removeFromCart = createAction(
'[Cart] Remove Item',
props<{ productId: number }>()
);
export const updateCartItem = createAction(
'[Cart] Update Item',
props<{ productId: number; quantity: number }>()
);
// Filter Actions
export const setCategoryFilter = createAction(
'[Filters] Set Category',
props<{ category: string }>()
);
export const setPriceRangeFilter = createAction(
'[Filters] Set Price Range',
props<{ min: number; max: number }>()
);
export const setSortBy = createAction(
'[Filters] Set Sort By',
props<{ sortBy: string }>()
);
```
## 4. Reducers
```typescript
// products.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as ProductsActions from './products.actions';
const initialState: ProductsState = {
products: [],
loading: false,
error: null
};
export const productsReducer = createReducer(
initialState,
on(ProductsActions.loadProducts, (state) => ({
...state,
loading: true,
error: null
})),
on(ProductsActions.loadProductsSuccess, (state, { products }) => ({
...state,
products,
loading: false
})),
on(ProductsActions.loadProductsFailure, (state, { error }) => ({
...state,
error,
loading: false
}))
);
// cart.reducer.ts
export const cartReducer = createReducer(
initialState,
on(CartActions.addToCart, (state, { product, quantity }) => {
const existingItem = state.items.find(item => item.product.id === product.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.product.id === product.id
? { ...item, quantity: item.quantity + quantity }
: item
)
};
}
return {
...state,
items: [...state.items, { product, quantity }]
};
})
);
```
## 5. Selectors
```typescript
// products.selectors.ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
export const selectProductsState = createFeatureSelector<ProductsState>('products');
export const selectCartState = createFeatureSelector<CartState>('cart');
export const selectFilterState = createFeatureSelector<FilterState>('filters');
export const selectAllProducts = createSelector(
selectProductsState,
(state: ProductsState) => state.products
);
export const selectFilteredProducts = createSelector(
selectAllProducts,
selectFilterState,
(products, filters) => {
return products
.filter(product =>
(!filters.category || product.category === filters.category) &&
product.price >= filters.priceRange.min &&
product.price <= filters.priceRange.max
)
.sort((a, b) => {
switch (filters.sortBy) {
case 'price': return a.price - b.price;
case 'name': return a.name.localeCompare(b.name);
case 'rating': return b.rating - a.rating;
default: return 0;
}
});
}
);
export const selectCartTotal = createSelector(
selectCartState,
(state: CartState) => state.items.reduce((total, item) =>
total + (item.product.price * item.quantity), 0
)
);
```
## 6. Effects voor Asynchrone Operaties
```typescript
// products.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { ProductService } from '../services/product.service';
import * as ProductsActions from './products.actions';
@Injectable()
export class ProductsEffects {
loadProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(ProductsActions.loadProducts),
mergeMap(() =>
this.productService.getProducts().pipe(
map(products => ProductsActions.loadProductsSuccess({ products })),
catchError(error => of(ProductsActions.loadProductsFailure({ error })))
)
)
)
);
constructor(
private actions$: Actions,
private productService: ProductService
) {}
}
```
## 7. Service voor API Calls
```typescript
// product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ProductService {
private apiUrl = 'https://api.yourdomain.com/products';
constructor(private http: HttpClient) {}
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl);
}
}
```
## 8. App Module Configuratie
```typescript
// app.module.ts
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
@NgModule({
imports: [
StoreModule.forRoot({
products: productsReducer,
cart: cartReducer,
filters: filterReducer
}),
EffectsModule.forRoot([ProductsEffects]),
StoreDevtoolsModule.instrument({
maxAge: 25,
logOnly: environment.production
})
]
})
export class AppModule { }
```
## 9. Gebruik in Componenten
```typescript
// product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { loadProducts, setCategoryFilter } from './store/products.actions';
import { selectFilteredProducts } from './store/products.selectors';
@Component({
selector: 'app-product-list',
template: `
<div *ngFor="let product of products$ | async">
{{ product.name }} - {{ product.price }}
</div>
`
})
export class ProductListComponent implements OnInit {
products$: Observable<Product[]>;
constructor(private store: Store<AppState>) {
this.products$ = this.store.select(selectFilteredProducts);
}
ngOnInit() {
this.store.dispatch(loadProducts());
}
filterByCategory(category: string) {
this.store.dispatch(setCategoryFilter({ category }));
}
}
```
## 10. Optimalisatie Technieken
### Memoized Selectors
```typescript
export const selectProductsByCategory = (category: string) =>
createSelector(selectAllProducts, products =>
products.filter(p => p.category === category)
);
```
### Entity Adapter voor Producten
```typescript
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
export interface ProductsState extends EntityState<Product> {
loading: boolean;
error: string | null;
}
export const adapter: EntityAdapter<Product> = createEntityAdapter<Product>();
const initialState: ProductsState = adapter.getInitialState({
loading: false,
error: null
});
```
### Debounce voor Filters
```typescript
// Gebruik RxJS debounceTime in effects
filterChanges$ = createEffect(() =>
this.actions$.pipe(
ofType(setCategoryFilter, setPriceRangeFilter),
debounceTime(300),
// Verwerk filterwijzigingen
)
);
```
## 11. Best Practices
1. **Normaliseer State**: Gebruik Entity Adapter voor betere performance
2. **Compose Selectors**: Bouw complexe selectors uit eenvoudige selectors
3. **Use OnPush Change Detection**: Voor betere performance in componenten
4. **Error Handling**: Implementeer goede error handling in effects
5. **Loading States**: Toon loading indicators tijdens async operaties
Deze implementatie biedt een schaalbare, onderhoudbare en performante state management oplossing voor je productcatalogus met winkelwagenfunctionaliteit.
error: Generation failed [deepseek] prompt_id=1456 locale=en err=DeepSeek connection/timeout