Angular の
状態管理 により、アプリケーションのすべての部分でデータが一貫して効率的に共有されることが保証されます。各コンポーネントが独自のデータを管理する代わりに、中央ストアが状態を保持します。
この一元化により、データが変更されると、すべてのコンポーネントが更新された状態を自動的に反映し、一貫した動作とよりシンプルなコードが実現されます。また、データ フローが単一の信頼できる情報源から管理されるため、アプリの保守と拡張が容易になります。
この記事では、簡単なショッピング カート アプリケーションを構築することで、NgRx を使用して Angular で状態管理を実装する方法を検討します。 ストア、アクション、リデューサー、セレクター、、エフェクト などの NgRx の中心的な概念を取り上げ、これらの部分がどのように連携してアプリケーションの状態を管理するかを示します。効果的に。
Angular のState は、ショッピング カートの内容など、アプリが管理および表示する必要があるデータを指します。
1.一貫性: すべてのコンポーネントにわたってデータが均一であることを保証します。 1 か所のデータが変更されると、中央ストアが関連するすべてのコンポーネントを自動的に更新し、不整合を防ぎます。
2.簡素化されたデータ フロー: コンポーネント間でデータを手動で渡す代わりに、状態管理により、すべてのコンポーネントが中央ストアから直接データにアクセスしたり更新したりできるため、アプリのデータ フローの管理と理解が容易になります。
3.より簡単なメンテナンスとスケーラビリティ: データ管理を一元化することで、状態管理によりコードの重複と複雑さが軽減されます。これにより、アプリの保守、デバッグ、拡張が容易になります。
4.パフォーマンスの最適化: 状態管理ソリューションには、アプリケーション全体を再レンダリングするのではなく、状態の変化に対応する必要があるコンポーネントのみを選択的に更新するなど、パフォーマンスを最適化するためのツールが付属していることがよくあります。
NgRx は、アプリケーションの状態を予測可能な方法で管理および維持するのに役立つ Angular の状態管理ライブラリです。
1.コンポーネント
コンポーネントは、ユーザーがアプリと対話する場所です。ショッピング カートに商品を追加するボタンである可能性があります。
コンポーネントとサービスは分離されており、相互に直接通信することはありません。代わりに サービス は エフェクト 内で使用されるため、従来の Angular アプリとは異なるアプリケーション構造が作成されます。
2.アクション
アクションは何が起こったかを説明し、必要なペイロード (データ) を含みます。
3.レデューサー
アクションに基づいて状態を更新します。
4.ストア
ストアは、アプリケーションの状態全体を保持する一元的な場所です。
5.セレクター
ストアからコンポーネントのデータを抽出します。
6.効果
エフェクトは、API 呼び出しなど、リデューサーに属さないロジックを処理する場所です。
7.サービス
サービスは実際のビジネス ロジックまたは API 呼び出しを実行します。エフェクトは多くの場合、サーバーからのデータの取得などのタスクを実行するためにサービスを使用します。
アプリの複雑さが正当な場合は NgRx を使用しますが、単純なアプリの場合は、より単純な状態管理方法を使用してください。コンポーネント間のコンポーネント間の Angular の services、signals、および @Input/@Output バインディングは、通常、それほど複雑でないアプリケーションの状態を管理するのに十分です。
1.新しい Angular プロジェクトを作成します:
ng new shopping-cart
2. NGRX とエフェクトをインストールします
NGRX とエフェクトをインストールするには、ターミナルで次のコマンドを実行します:
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: カート アクションを定義する
state フォルダーに 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: レデューサーを作成する
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.
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 中国語 Web サイトの他の関連記事を参照してください。