«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Освоение управления состоянием Angular с использованием NgRx

Освоение управления состоянием Angular с использованием NgRx

Опубликовано 2 ноября 2024 г.
Просматривать:706

State management in Angular ensures that data is consistently and efficiently shared across all parts of an application. Instead of each component managing its own data, a central store holds the state.

This centralization ensures that when data changes, all components automatically reflect the updated state, leading to consistent behavior and simpler code. It also makes the app easier to maintain and scale, as data flow is managed from a single source of truth.

In this article, we’ll explore how to implement state management in Angular using NgRx by building a simple shopping cart application. We’ll cover the core concepts of NgRx, such as the Store, Actions, Reducers, Selectors, and Effects, and demonstrate how these pieces fit together to manage the state of your application effectively.

State in Angular refers to the data your app needs to manage and display, like a shopping cart’s contents.

Why you need State Management

1. Consistency: It ensures that data is uniform across all components. When data changes in one place, the central store updates all relevant components automatically, preventing inconsistencies.

2. Simplified Data Flow: Instead of passing data between components manually, state management allows any component to access or update data directly from the central store, making the app’s data flow easier to manage and understand.

3. Easier Maintenance and Scalability: By centralizing data management, state management reduces code duplication and complexity. This makes the app easier to maintain, debug, and scale as it grows.

4. Performance Optimization: State management solutions often come with tools to optimize performance, such as selectively updating only the components that need to react to a change in state, rather than re-rendering the entire application.

How NgRx works

NgRx is a state management library for Angular that helps manage and maintain the state of your application in a predictable way.

Mastering Angular State Management using NgRx

1. Component

The component is where the user interacts with your app. It might be a button to add an item to the shopping cart.

Components and services are separated and don’t communicate with each other directly, instead services are used within effects thus creating an application structure different from a traditional Angular app.

2. Action

An action describes what happened and contains any necessary payload (data).

3. Reducer

Updates the state based on the action.

4. Store

The store is a centralized place that holds the entire state of your application.

5. Selector

Extracts data from the store for components.

6. Effects

Effects are where you handle logic that doesn’t belong in the reducer, like API calls.

7. Service

Services perform the actual business logic or API calls. Effects often use services to perform tasks like fetching data from a server.

When to Use NgRx

Use NgRx when your app’s complexity justifies it, but for straightforward apps, stick to simpler state management methods. Angular’s services, signals and @Input/@Output bindings between components are usually sufficient for managing state in less complex applications.

Example: Building an Add to Cart Feature with NgRx

1.Create a New Angular Project:

ng new shopping-cart

2. Install NGRX and Effects
To install NGRX and Effects, run the following command in your terminal:

ng add @ngrx/store@latest

ng add @ngrx/effects

3. Define the Product Model
Inside the src/app directory, create a file named product.model.ts

Define the Product interface to represent the structure of a product:

export interface Product {
    id: string;
    name: string;
    price: number;
    quantity: number;
}

4. Set Up State Management
Step 1: Create state Folder inside the src/app directory

Step 2: Define Cart Actions

Create cart.actions.ts in the state folder.

import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Product } from '../product.model';

export const CartActions = createActionGroup({
  source: 'Cart',
  events: {
    'Add Product': props(),
    'Remove Product': props(),
    'Update Quantity': props(),
    'Load Products': emptyProps,
  },
});

export const CartApiActions = createActionGroup({
  source: 'Cart API',
  events: {
    'Load Products Success': props(),
    'Load Products Failure': props(),
  },
});

Step 3: Create Reducers

Create cart.reducer.ts in the state folder.

import { createReducer, on } from '@ngrx/store';
import { Product } from '../product.model';
import { CartActions, CartApiActions } from './cart.actions';

// Initial state for products and cart
export const initialProductsState: ReadonlyArray = [];
export const initialCartState: ReadonlyArray = [];

// Reducer for products (fetched from API)
export const productsReducer = createReducer(
  initialProductsState,
  on(CartApiActions.loadProductsSuccess, (_state, { products }) => products)
);

// Reducer for cart (initially empty)
export const cartReducer = createReducer(
  initialCartState,
  on(CartActions.addProduct, (state, { product }) => {
    const existingProduct = state.find(p => p.id === product.id);
    if (existingProduct) {
      return state.map(p =>
        p.id === product.id ? { ...p, quantity: p.quantity   product.quantity } : p
      );
    }
    return [...state, product];
  }),
  on(CartActions.removeProduct, (state, { productId }) =>
    state.filter(p => p.id !== productId)
  ),
  on(CartActions.updateQuantity, (state, { productId, quantity }) =>
    state.map(p =>
      p.id === productId ? { ...p, quantity } : p
    )
  )
);

Step 4: Create Selectors

In the state folder, create cart.selectors.ts

import { createSelector, createFeatureSelector } from '@ngrx/store';
import { Product } from '../product.model';

export const selectProducts = createFeatureSelector>('products');

export const selectCart = createFeatureSelector>('cart');

export const selectCartTotal = createSelector(selectCart, (cart) =>
  cart.reduce((total, product) => total   product.price * product.quantity, 0)
);

Step 5: Create Effects

Create a new file cart.effects.ts in the state folder that listens for the Load Products action, uses the service to fetch products, and dispatches either a success or failure action.

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { ProductService } from '../product.service';
import { CartActions, CartApiActions } from './cart.actions';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class CartEffects {
  loadProducts$ = createEffect(() =>
    this.actions$.pipe(
      ofType(CartActions.loadProducts),
      mergeMap(() =>
        this.productService.getProducts().pipe(
          map(products => CartApiActions.loadProductsSuccess({ products })),
          catchError(error => of(CartApiActions.loadProductsFailure({ error })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private productService: ProductService
  ) {}
}

5. Connect the State Management to Your App
In a file called app.config.ts, set up configurations for providing the store and effects to the application.

import { ApplicationConfig } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { provideHttpClient } from '@angular/common/http';
import { cartReducer, productsReducer } from './state/cart.reducer';
import { provideEffects } from '@ngrx/effects';
import { CartEffects } from './state/cart.effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore({
      products: productsReducer, 
      cart: cartReducer 
    }),
    provideHttpClient(),
    provideEffects([CartEffects])
],
};

6. Create a Service to Fetch Products
In the src/app directory create product.service.ts to implement the service to fetch products

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Product } from './product.model';

@Injectable({ providedIn: 'root' })
export class ProductService {
  getProducts(): Observable> {
    return of([
      { id: '1', name: 'Product 1', price: 10, quantity: 1 },
      { id: '2', name: 'Product 2', price: 20, quantity: 1 },
    ]);
  }
}

7. Create the Product List Component
Run the following command to generate the component: ng generate component product-list

This component displays the list of products and allows adding them to the cart.

Modify the product-list.component.ts file:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Product } from '../product.model';
import { selectProducts } from '../state/cart.selectors';
import { CartActions } from '../state/cart.actions';

@Component({
  selector: 'app-product-list',
  standalone: true,
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css'],
  imports: [CommonModule],
})
export class ProductListComponent implements OnInit {
  products$!: Observable>;

  constructor(private store: Store) {

  }

  ngOnInit(): void {
    this.store.dispatch(CartActions.loadProducts()); // Dispatch load products action
    this.products$ = this.store.select(selectProducts); // Select products from the store
  }

  onAddToCart(product: Product) {
    this.store.dispatch(CartActions.addProduct({ product }));
  }
}

Modify the product-list.component.html file:

{{product.name}}

{{product.price | currency}}

8. Create the Shopping Cart Component
Run the following command to generate the component: ng generate component shopping-cart

This component displays the products in the cart and allows updating the quantity or removing items from the cart.

Modify the shopping-cart.component.ts file:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Product } from '../product.model';
import { selectCart, selectCartTotal } from '../state/cart.selectors';
import { CartActions } from '../state/cart.actions';

@Component({
  selector: 'app-shopping-cart',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './shopping-cart.component.html',
  styleUrls: ['./shopping-cart.component.css'],
})
export class ShoppingCartComponent implements OnInit {
  cart$: Observable>;
  cartTotal$: Observable;

  constructor(private store: Store) {
    this.cart$ = this.store.select(selectCart);
    this.cartTotal$ = this.store.select(selectCartTotal);
  }

  ngOnInit(): void {}

  onRemoveFromCart(productId: string) {
    this.store.dispatch(CartActions.removeProduct({ productId }));
  }

  onQuantityChange(event: Event, productId: string) {
    const inputElement = event.target as HTMLInputElement;
    let quantity = parseInt(inputElement.value, 10);

    this.store.dispatch(CartActions.updateQuantity({ productId, quantity }));
  }
}

Modify the shopping-cart.component.html file:

{{product.name}}

{{product.price | currency}}
Total: {{cartTotal$ | async | currency}}

Modify the shopping-cart.component.css file:

.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.cart-item p {
  margin: 0;
  font-size: 16px;
}

.cart-item input {
  width: 50px;
  text-align: center;
}

.total {
  font-weight: bold;
  margin-top: 20px;
}

9. Put Everything Together in the App Component
This component will display the product list and the shopping cart

Modify the app.component.ts file:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product-list/product-list.component';
import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component';
import { NgIf } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  imports: [CommonModule, ProductListComponent, ShoppingCartComponent, NgIf],
})
export class AppComponent {}

Modify the app.component.html file:


Products

Shopping Cart

10. Running the Application
Finally, run your application using ng serve.

Now, you can add products to your cart, remove them, or update their quantities.

Conclusion

In this article, we built a simple shopping cart application to demonstrate the core concepts of NgRx, such as the Store, Actions, Reducers, Selectors, and Effects. This example serves as a foundation for understanding how NgRx works and how it can be applied to more complex applications.

As your Angular projects grow in complexity, leveraging NgRx for state management will help you maintain consistency across your application, reduce the likelihood of bugs, and make your codebase easier to maintain.

To get the code for the above project, click the link below:
https://github.com/anthony-kigotho/shopping-cart

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/bytebantz/mastering-angular-18-state-management-using-ngrx-5fao?1. Если есть какие-либо нарушения, свяжитесь с [email protected], чтобы удалить их.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3