NgRx を使用して Angular 状態管理をマスターする

WBOY
リリース: 2024-09-10 16:30:36
オリジナル
534 人が閲覧しました

Angular の

状態管理 により、アプリケーションのすべての部分でデータが一貫して効率的に共有されることが保証されます。各コンポーネントが独自のデータを管理する代わりに、中央ストアが状態を保持します。

この一元化により、データが変更されると、すべてのコンポーネントが更新された状態を自動的に反映し、一貫した動作とよりシンプルなコードが実現されます。また、データ フローが単一の信頼できる情報源から管理されるため、アプリの保守と拡張が容易になります。

この記事では、簡単なショッピング カート アプリケーションを構築することで、NgRx を使用して Angular で状態管理を実装する方法を検討します。 ストア、アクション、リデューサー、セレクター、エフェクト などの NgRx の中心的な概念を取り上げ、これらの部分がどのように連携してアプリケーションの状態を管理するかを示します。効果的に。

Angular の

State は、ショッピング カートの内容など、アプリが管理および表示する必要があるデータを指します。

状態管理が必要な理由

1.一貫性: すべてのコンポーネントにわたってデータが均一であることを保証します。 1 か所のデータが変更されると、中央ストアが関連するすべてのコンポーネントを自動的に更新し、不整合を防ぎます。

2.簡素化されたデータ フロー: コンポーネント間でデータを手動で渡す代わりに、状態管理により、すべてのコンポーネントが中央ストアから直接データにアクセスしたり更新したりできるため、アプリのデータ フローの管理と理解が容易になります。

3.より簡単なメンテナンスとスケーラビリティ: データ管理を一元化することで、状態管理によりコードの重複と複雑さが軽減されます。これにより、アプリの保守、デバッグ、拡張が容易になります。

4.パフォーマンスの最適化: 状態管理ソリューションには、アプリケーション全体を再レンダリングするのではなく、状態の変化に対応する必要があるコンポーネントのみを選択的に更新するなど、パフォーマンスを最適化するためのツールが付属していることがよくあります。

NgRx の仕組み

NgRx は、アプリケーションの状態を予測可能な方法で管理および維持するのに役立つ Angular の状態管理ライブラリです。

Mastering Angular State Management using NgRx

1.コンポーネント

コンポーネントは、ユーザーがアプリと対話する場所です。ショッピング カートに商品を追加するボタンである可能性があります。

コンポーネントとサービスは分離されており、相互に直接通信することはありません。代わりに サービスエフェクト 内で使用されるため、従来の Angular アプリとは異なるアプリケーション構造が作成されます。

2.アクション

アクションは何が起こったかを説明し、必要なペイロード (データ) を含みます。

3.レデューサー

アクションに基づいて状態を更新します。

4.ストア

ストアは、アプリケーションの状態全体を保持する一元的な場所です。

5.セレクター

ストアからコンポーネントのデータを抽出します。

6.効果

エフェクトは、API 呼び出しなど、リデューサーに属さないロジックを処理する場所です。

7.サービス

サービスは実際のビジネス ロジックまたは API 呼び出しを実行します。エフェクトは多くの場合、サーバーからのデータの取得などのタスクを実行するためにサービスを使用します。

NgRx を使用する場合

アプリの複雑さが正当な場合は NgRx を使用しますが、単純なアプリの場合は、より単純な状態管理方法を使用してください。コンポーネント間のコンポーネント間の Angular の servicessignals、および @Input/@Output バインディングは、通常、それほど複雑でないアプリケーションの状態を管理するのに十分です。

例: NgRx を使用してカートに追加機能を構築する

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.

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 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート