」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 使用 NgRx 實體簡化您的 Angular 程式碼

使用 NgRx 實體簡化您的 Angular 程式碼

發佈於2024-11-12
瀏覽:353

Simplify Your Angular Code with NgRx Entities

In the summer, I refreshed my NgRx skills by building a small application to handle my favorite places. During that process, I enjoyed NgRx because I had real control over the state of my app.

One thing that caused a lot of noise was the number of selectors and actions to define for CRUD operations. In my personal project, it wasn't too much trouble, but when I was building a large application with many slices and sections, along with selectors and reducers, the code became harder to maintain.

For example, writing actions for success, error, update, and delete, along with selectors for each operation, increased the complexity and required more testing.

That's where NgRx Entities come in. NgRx Entities reduce boilerplate code, simplify testing, speed up delivery times, and keep the codebase more maintainable. In this article, I'll walk you through refactoring the state management of places in my project using NgRx Entities to simplify CRUD logic.

What and Why NgRx Entities?

Before diving into code, let's first understand what NgRx Entities are and why you should consider using them.

What is @NgRx/Entities

NgRx Entities is an extension of NgRx that simplifies working with data collections. It provides a set of utilities that make it easy to perform operations like adding, updating, and removing entities from the state, as well as selecting entities from the store.

Why Do I Need to Move to NgRx Entities?

When building CRUD operations for collections, manually writing methods in the reducer and creating repetitive selectors for each operation can be tedious and error-prone. NgRx Entities offloads much of this responsibility, reducing the amount of code you need to write and maintain. By minimizing boilerplate code, NgRx Entities helps lower technical debt and simplify state management in larger applications.

How Does It Work?

NgRx Entities provides tools such as EntityState, EntityAdapter, and predefined selectors to streamline working with collections.

EntityState

The EntityState interface is the core of NgRx Entities. It stores the collection of entities using two key properties:

  • ids: an array of entity IDs.

  • entities: a dictionary where each entity is stored by its ID.

interface EntityState {
  ids: string[] | number[];
  entities: { [id: string | id: number]: V };
}

Read more about Entity State

EntityAdapter

The EntityAdapter is created using the createEntityAdapter function. It provides many helper methods for managing entities in the state, such as adding, updating, and removing entities. Additionally, you can configure how the entity is identified and sorted.

export const adapter: EntityAdapter = createEntityAdapter();

The EntityAdapter also allows you to define how entities are identified (selectId) and how the collection should be sorted using the sortComparer.

Read more about EntityAdapter

Now that we understand the basics, let's see how we can refactor the state management of places in our application using NgRx Entities

Setup Project

First, clone the repository from the previous article and switch to the branch that has the basic CRUD setup:

git clone https://github.com/danywalls/start-with-ngrx.git
git checkout crud-ngrx
cd start-with-ngrx

?This article is part of my series on learning NgRx. If you want to follow along, please check it out.

https://www.danywalls.com/understanding-when-and-why-to-implement-ngrx-in-angular

https://www.danywalls.com/how-to-debug-ngrx-using-redux-devtools

https://www.danywalls.com/how-to-implement-actioncreationgroup-in-ngrx

https://www.danywalls.com/how-to-use-ngrx-selectors-in-angular

https://danywalls.com/when-to-use-concatmap-mergemap-switchmap-and-exhaustmap-operators-in-building-a-crud-with-ngrx

https://danywalls.com/handling-router-url-parameters-using-ngrx-router-store

This branch contains the setup where NgRx is already installed, and MockAPI.io is configured for API calls.

Our goal is to use NgRx entities to manage places, refactor actions for CRUD operations, update the reducer to simplify it using adapter operations like adding, updating, and deleting places, use selectors to retrieve the list of places from the store.

Installing NgRx Entities

First, install the project dependencies with npm i, and then add NgRx Entities using schematics by running ng add @ngrx/entity.

npm i
ng add @ngrx/entity

Perfect, we are ready to start our refactor!

Refactoring the State

In the previous version of the project, we manually defined arrays and reducers to manage the state. With NgRx Entities, we let the adapter manage the collection logic for us.

First, open places.state.ts and refactor the PlacesState to extend from EntityState.

export type PlacesState = {
  placeSelected: Place | undefined;
  loading: boolean;
  error: string | undefined;
} & EntityState;

Next, initialize the entity adapter for our Place entity using createEntityAdapter:

const adapter: EntityAdapter = createEntityAdapter();

Finally, replace the manual initialState with the one provided by the adapter using getInitialState:

export const placesInitialState = adapter.getInitialState({
  error: '',
  loading: false,
  placeSelected: undefined,
});

We've refactored the state to use EntityState and initialized the EntityAdapter to handle the list of places automatically.

let's move to update the actions to use NgRx Entities.

Refactoring the Actions

In the previous articles, I manually handled updates and modifications to entities. Now, we will use NgRx Entities to handle partial updates using Update.

In places.actions.ts, we update the Update Place action to use Update, which allows us to modify only part of an entity:

import { Update } from '@ngrx/entity';

export const PlacesPageActions = createActionGroup({
  source: 'Places',
  events: {
    'Load Places': emptyProps(),
    'Add Place': props(),
    'Update Place': props }>(), // Use Update
    'Delete Place': props(),
    'Select Place': props(),
    'UnSelect Place': emptyProps(),
  },
});

Perfect, we updated the actions to work with NgRx Entities, using the Update type to simplify handling updates. It's time to see how this impacts the reducer and refactor it to use the entity adapter methods for operations like adding, updating, and removing places.

Refactoring the Reducer

The reducer is where NgRx Entities really shines. Instead of writing manual logic for adding, updating, and deleting places, we now use methods provided by the entity adapter.

Here’s how we can simplify the reducer:

import { createReducer, on } from '@ngrx/store';
import { adapter, placesInitialState } from './places.state';
import { PlacesApiActions, PlacesPageActions } from './places.actions';

export const placesReducer = createReducer(
  placesInitialState,
  on(PlacesPageActions.loadPlaces, (state) => ({
    ...state,
    loading: true,
  })),
  on(PlacesApiActions.loadSuccess, (state, { places }) => 
    adapter.setAll(places, { ...state, loading: false })
  ),
  on(PlacesApiActions.loadFailure, (state, { message }) => ({
    ...state,
    loading: false,
    error: message,
  })),
  on(PlacesPageActions.addPlace, (state, { place }) =>
    adapter.addOne(place, state)
  ),
  on(PlacesPageActions.updatePlace, (state, { update }) =>
    adapter.updateOne(update, state)
  ),
  on(PlacesPageActions.deletePlace, (state, { id }) =>
    adapter.removeOne(id, state)
  )
);

We’ve used methods like addOne, updateOne, removeOne, and setAll from the adapter to handle entities in the state.

Other useful methods include:

  • addMany: Adds multiple entities.

  • removeMany: Removes multiple entities by ID.

  • upsertOne: Adds or updates an entity based on its existence.

Read more about reducer methods in the EntityAdapter.

With the state, actions, and reducers refactored, we’ll now refactor the selectors to take advantage of NgRx Entities’ predefined selectors.

Refactoring the Selectors

NgRx Entities provides a set of predefined selectors that make querying the store much easier. I will use selectors like selectAll, selectEntities, and selectIds directly from the adapter.

Here’s how we refactor the selectors in places.selectors.ts:

import { createFeatureSelector } from '@ngrx/store';
import { adapter, PlacesState } from './places.state';

const selectPlaceState = createFeatureSelector('places');

const { selectAll, selectEntities, selectIds, selectTotal } = adapter.getSelectors(selectPlaceState);

These built-in selectors significantly reduce the need to manually create selectors for accessing state.

After refactoring the selectors to use the predefined ones, reducing the need to manually define my selectors, it is time to update our form components to reflect these changes and use the new state and actions.

Updating the Form Components

Now that we have the state, actions, and reducers refactored, we need to update the form components to reflect these changes.

For example, in PlaceFormComponent, we can update the save method to use the Update type when saving changes:

import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { FormsModule } from '@angular/forms';
import { AsyncPipe } from '@angular/common';
import { selectSelectedPlace } from '../../pages/places/state/places.selectors';
import { PlacesPageActions } from '../../pages/places/state/places.actions';
import { Place } from '../../entities/place.model';

@Component({
  selector: 'app-place-form',
  standalone: true,
  imports: [FormsModule, AsyncPipe],
  templateUrl: './place-form.component.html',
  styleUrls: ['./place-form.component.scss'],
})
export class PlaceFormComponent {
  store = inject(Store);


  placeSelected$ = this.store.select(selectSelectedPlace);

  delete(id: string) {
    this.store.dispatch(PlacesPageActions.deletePlace({ id }));
  }

  save(place: Place, name: string) {
    const update = { id: place.id, changes: { name } }; 
    this.store.dispatch(PlacesPageActions.updatePlace({ update }));
  }
}

We updated our form components to use the new actions and state refactored, lets move , let’s check our effects to ensure they work correctly with NgRx Entities

Refactoring Effects

Finally, I will make the effects work with NgRx Entities, we only need to update the PlacesPageActions.updatePlace pass the correct Update object in the updatePlaceEffect$ effect.

export const updatePlaceEffect$ = createEffect(
  (actions$ = inject(Actions), placesService = inject(PlacesService)) => {
    return actions$.pipe(
      ofType(PlacesPageActions.updatePlace),
      concatMap(({ update }) =>
        placesService.update(update.changes).pipe(
          map((updatedPlace) =>
            PlacesApiActions.updateSuccess({ place: updatedPlace })
          ),
          catchError((error) =>
            of(PlacesApiActions.updateFailure({ message: error })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);

Done! I did our app is working with NgRx Entities and the migration was so easy !, the documentation of ngrx entity is very helpfull and

Conclusion

After moving my code to NgRx Entities, I felt it helped reduce complexity and boilerplate when working with collections. NgRx Entities simplify working with collections and interactions with its large number of methods for most scenarios, eliminating much of the boilerplate code needed for CRUD operations.

I hope this article motivates you to use ngrx-entities when you need to work with collections in ngrx.

  • source code: https://github.com/danywalls/start-with-ngrx/tree/ngrx-entities

Photo by Yonko Kilasi on Unsplash

版本聲明 本文轉載於:https://dev.to/danywalls/simplify-your-angular-code-with-ngrx-entities-1dgn?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何處理PHP文件系統功能中的UTF-8文件名?
    如何處理PHP文件系統功能中的UTF-8文件名?
    在PHP的Filesystem functions中處理UTF-8 FileNames 在使用PHP的MKDIR函數中含有UTF-8字符的文件很多flusf-8字符時,您可能會在Windows Explorer中遇到comploreer grounder grounder grounder gro...
    程式設計 發佈於2025-07-20
  • 在PHP中如何高效檢測空數組?
    在PHP中如何高效檢測空數組?
    在PHP 中檢查一個空數組可以通過各種方法在PHP中確定一個空數組。如果需要驗證任何數組元素的存在,則PHP的鬆散鍵入允許對數組本身進行直接評估:一種更嚴格的方法涉及使用count()函數: if(count(count($ playerList)=== 0){ //列表為空。 } 對...
    程式設計 發佈於2025-07-20
  • 如何使用FormData()處理多個文件上傳?
    如何使用FormData()處理多個文件上傳?
    )處理多個文件輸入時,通常需要處理多個文件上傳時,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    程式設計 發佈於2025-07-20
  • 如何將多種用戶類型(學生,老師和管理員)重定向到Firebase應用中的各自活動?
    如何將多種用戶類型(學生,老師和管理員)重定向到Firebase應用中的各自活動?
    Red: How to Redirect Multiple User Types to Respective ActivitiesUnderstanding the ProblemIn a Firebase-based voting app with three distinct user type...
    程式設計 發佈於2025-07-20
  • 如何正確使用與PDO參數的查詢一樣?
    如何正確使用與PDO參數的查詢一樣?
    在pdo 中使用類似QUERIES在PDO中的Queries時,您可能會遇到類似疑問中描述的問題:此查詢也可能不會返回結果,即使$ var1和$ var2包含有效的搜索詞。錯誤在於不正確包含%符號。 通過將變量包含在$ params數組中的%符號中,您確保將%字符正確替換到查詢中。沒有此修改,PD...
    程式設計 發佈於2025-07-20
  • 為什麼PYTZ最初顯示出意外的時區偏移?
    為什麼PYTZ最初顯示出意外的時區偏移?
    與pytz 最初從pytz獲得特定的偏移。例如,亞洲/hong_kong最初顯示一個七個小時37分鐘的偏移: 差異源利用本地化將時區分配給日期,使用了適當的時區名稱和偏移量。但是,直接使用DateTime構造器分配時區不允許進行正確的調整。 example pytz.timezone(&#...
    程式設計 發佈於2025-07-20
  • Python中何時用"try"而非"if"檢測變量值?
    Python中何時用"try"而非"if"檢測變量值?
    使用“ try“ vs.” if”來測試python 在python中的變量值,在某些情況下,您可能需要在處理之前檢查變量是否具有值。在使用“如果”或“ try”構建體之間決定。 “ if” constructs result = function() 如果結果: 對於結果: ...
    程式設計 發佈於2025-07-20
  • 在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在細胞編輯後,如何維護自定義的JTable細胞渲染?
    在JTable中維護jtable單元格渲染後,在JTable中,在JTable中實現自定義單元格渲染和編輯功能可以增強用戶體驗。但是,至關重要的是要確保即使在編輯操作後也保留所需的格式。 在設置用於格式化“價格”列的“價格”列,用戶遇到的數字格式丟失的“價格”列的“價格”之後,問題在設置自定義單元...
    程式設計 發佈於2025-07-20
  • 為什麼使用固定定位時,為什麼具有100%網格板柱的網格超越身體?
    為什麼使用固定定位時,為什麼具有100%網格板柱的網格超越身體?
    網格超過身體,用100%grid-template-columns 為什麼在grid-template-colms中具有100%的顯示器,當位置設置為設置的位置時,grid-template-colly修復了? 問題: 考慮以下CSS和html: class =“ snippet-code”> ...
    程式設計 發佈於2025-07-20
  • 為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    為什麼我會收到MySQL錯誤#1089:錯誤的前綴密鑰?
    mySQL錯誤#1089:錯誤的前綴鍵錯誤descript [#1089-不正確的前綴鍵在嘗試在表中創建一個prefix鍵時會出現。前綴鍵旨在索引字符串列的特定前綴長度長度,以便更快地搜索這些前綴。 理解prefix keys `這將在整個Movie_ID列上創建標準主鍵。主密鑰對於唯一識...
    程式設計 發佈於2025-07-20
  • 表單刷新後如何防止重複提交?
    表單刷新後如何防止重複提交?
    在Web開發中預防重複提交 在表格提交後刷新頁面時,遇到重複提交的問題是常見的。要解決這個問題,請考慮以下方法: 想像一下具有這樣的代碼段,看起來像這樣的代碼段:)){ //數據庫操作... 迴聲“操作完成”; 死(); } ? > ...
    程式設計 發佈於2025-07-20
  • 解決Spring Security 4.1及以上版本CORS問題指南
    解決Spring Security 4.1及以上版本CORS問題指南
    彈簧安全性cors filter:故障排除常見問題 在將Spring Security集成到現有項目中時,您可能會遇到與CORS相關的錯誤,如果像“訪問Control-allo-allow-Origin”之類的標頭,則無法設置在響應中。為了解決此問題,您可以實現自定義過濾器,例如代碼段中的MyFi...
    程式設計 發佈於2025-07-20
  • 如何在GO編譯器中自定義編譯優化?
    如何在GO編譯器中自定義編譯優化?
    在GO編譯器中自定義編譯優化 GO中的默認編譯過程遵循特定的優化策略。 However, users may need to adjust these optimizations for specific requirements.Optimization Control in Go Compi...
    程式設計 發佈於2025-07-20
  • 如何從PHP中的數組中提取隨機元素?
    如何從PHP中的數組中提取隨機元素?
    從陣列中的隨機選擇,可以輕鬆從數組中獲取隨機項目。考慮以下數組:; 從此數組中檢索一個隨機項目,利用array_rand( array_rand()函數從數組返回一個隨機鍵。通過將$項目數組索引使用此鍵,我們可以從數組中訪問一個隨機元素。這種方法為選擇隨機項目提供了一種直接且可靠的方法。
    程式設計 發佈於2025-07-20
  • Go語言垃圾回收如何處理切片內存?
    Go語言垃圾回收如何處理切片內存?
    Garbage Collection in Go Slices: A Detailed AnalysisIn Go, a slice is a dynamic array that references an underlying array.使用切片時,了解垃圾收集行為至關重要,以避免潛在的內存洩...
    程式設計 發佈於2025-07-20

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3