首页 > web前端 > js教程 > 使用 NgRx 掌握 Angular 状态管理

使用 NgRx 掌握 Angular 状态管理

WBOY
发布: 2024-09-10 16:30:36
原创
552 人浏览过

Angular 中的

状态管理可确保数据在应用程序的所有部分之间一致且高效地共享。中央存储保存状态,而不是每个组件管理自己的数据。

这种集中化确保当数据发生变化时,所有组件都会自动反映更新后的状态,从而实现一致的行为和更简单的代码。它还使应用程序更易于维护和扩展,因为数据流是从单一事实来源进行管理的。

在本文中,我们将通过构建一个简单的购物车应用程序来探索如何使用 NgRx 在 Angular 中实现状态管理。我们将介绍 NgRx 的核心概念,例如 Store、Actions、Reducers、Selectors、Effects,并演示这些部分如何组合在一起来管理应用程序的状态有效。

Angular 中的

状态 是指您的应用需要管理和显示的数据,例如购物车的内容。

为什么需要状态管理

1。一致性: 它确保所有组件中的数据是统一的。当一处数据发生变化时,中央存储会自动更新所有相关组件,防止不一致。

2。简化的数据流:状态管理允许任何组件直接从中央存储访问或更新数据,而不是在组件之间手动传递数据,从而使应用程序的数据流更易于管理和理解。

3。更轻松的维护和可扩展性:通过集中数据管理,状态管理减少了代码重复和复杂性。这使得应用程序在增长时更容易维护、调试和扩展。

4。性能优化:状态管理解决方案通常附带优化性能的工具,例如有选择地仅更新需要对状态变化做出反应的组件,而不是重新渲染整个应用程序。

NgRx 的工作原理

NgRx 是 Angular 的状态管理库,可帮助以可预测的方式管理和维护应用程序的状态。

Mastering Angular State Management using NgRx

1。组件

组件是用户与您的应用程序交互的地方。它可能是一个将商品添加到购物车的按钮。

组件和服务是分开的,不直接相互通信,而是在效果中使用服务,从而创建与传统​​Angular应用程序不同的应用程序结构。

2。行动

操作描述发生的事情并包含任何必要的有效负载(数据)。

3。减速器

根据操作更新状态。

4。商店

商店是一个集中的地方,保存应用程序的整个状态。

5。选择器

从组件存储中提取数据。

6。效果

效果是你处理不属于reducer的逻辑的地方,比如API调用。

7。服务

服务执行实际的业务逻辑或 API 调用。效果通常使用服务来执行任务,例如从服务器获取数据。

何时使用 NgRx

当应用程序的复杂性证明合理时,请使用 NgRx,但对于简单的应用程序,请坚持使用更简单的状态管理方法。 Angular 的 servicessignals@Input/@Output 组件之间的绑定通常足以管理不太复杂的应用程序中的状态。

示例:使用 NgRx 构建“添加到购物车”功能

1.创建一个新的 Angular 项目:

ng new shopping-cart
登录后复制

2。安装 NGRX 和效果
要安装 NGRX 和 Effects,请在终端中运行以下命令:

ng add @ngrx/store@latest

ng add @ngrx/effects
登录后复制

3。定义产品模型
src/app 目录中,创建一个名为 product.model.ts

的文件

定义Product接口来表示产品的结构:

export interface Product {
    id: string;
    name: string;
    price: number;
    quantity: number;
}
登录后复制

4。设置状态管理
第 1 步:在 src/app 目录中创建 state 文件夹

第 2 步:定义购物车操作

在状态文件夹中创建cart.actions.ts

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

export const CartActions = createActionGroup({
  source: 'Cart',
  events: {
    'Add Product': props<{ product: Product }>(),
    'Remove Product': props<{ productId: string }>(),
    'Update Quantity': props<{ productId: string; quantity: number }>(),
    'Load Products': emptyProps,
  },
});

export const CartApiActions = createActionGroup({
  source: 'Cart API',
  events: {
    'Load Products Success': props<{ products: Product[] }>(),
    'Load Products Failure': props<{ error: string }>(),
  },
});
登录后复制

第3步:创建Reducers

state 文件夹中创建 cart.reducer.ts

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<Product> = [];
export const initialCartState: ReadonlyArray<Product> = [];

// 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
    )
  )
);
登录后复制

第 4 步:创建选择器

state 文件夹中,创建 cart.selectors.ts

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

export const selectProducts = createFeatureSelector<ReadonlyArray<Product>>('products');

export const selectCart = createFeatureSelector<ReadonlyArray<Product>>('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<Array<Product>> {
    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<ReadonlyArray<Product>>;

  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:

<div *ngIf="products$ | async as products">
  <div class="product-item" *ngFor="let product of products">
    <p>{{product.name}}</p>
    <span>{{product.price | currency}}</span>
    <button (click)="onAddToCart(product)" data-test="add-button">Add to Cart</button>
  </div>
</div>
登录后复制

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<ReadonlyArray<Product>>;
  cartTotal$: Observable<number>;

  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:

<div *ngIf="cart$ | async as cart">
  <div class="cart-item" *ngFor="let product of cart">
    <p>{{product.name}}</p><span>{{product.price | currency}}</span>
    <input type="number" [value]="product.quantity" (input)="onQuantityChange($event, product.id)" />
    <button (click)="onRemoveFromCart(product.id)" data-test="remove-button">Remove</button>
  </div>
  <div class="total">
    Total: {{cartTotal$ | async | currency}}
  </div>
</div>
登录后复制

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:

<!-- app.component.html -->
<h2>Products</h2>
<app-product-list></app-product-list>

<h2>Shopping Cart</h2>
<app-shopping-cart></app-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

以上是使用 NgRx 掌握 Angular 状态管理的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板