”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 NgRx 实体简化您的 Angular 代码

使用 NgRx 实体简化您的 Angular 代码

发布于2024-11-12
浏览:368

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]删除
最新教程 更多>
  • 大批
    大批
    方法是可以在对象上调用的 fns 数组是对象,因此它们在 JS 中也有方法。 slice(begin):将数组的一部分提取到新数组中,而不改变原始数组。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index p...
    编程 发布于2024-11-16
  • 如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    在 PHP 中组合关联数组在 PHP 中,将两个关联数组组合成一个数组是一项常见任务。考虑以下请求:问题描述:提供的代码定义了两个关联数组,$array1和$array2。目标是创建一个新数组 $array3,它合并两个数组中的所有键值对。 此外,提供的数组具有唯一的 ID,而名称可能重合。要求是构...
    编程 发布于2024-11-16
  • 除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    除了“if”语句之外:还有什么地方可以在不进行强制转换的情况下使用具有显式“bool”转换的类型?
    无需强制转换即可上下文转换为 bool您的类定义了对 bool 的显式转换,使您能够在条件语句中直接使用其实例“t”。然而,这种显式转换提出了一个问题:“t”在哪里可以在不进行强制转换的情况下用作 bool?上下文转换场景C 标准指定了四种值可以根据上下文转换为 bool 的主要场景:语句:if、w...
    编程 发布于2024-11-16
  • Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta:列偏移的删除和恢复Bootstrap 4 在其 Beta 1 版本中引入了重大更改柱子偏移了。然而,随着 Beta 2 的后续发布,这些变化已经逆转。从 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    编程 发布于2024-11-16
  • 在 Go 中使用 WebSocket 进行实时通信
    在 Go 中使用 WebSocket 进行实时通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要一种比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSoc...
    编程 发布于2024-11-16
  • 如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 识别今天生日的用户使用 MySQL 确定今天是否是用户的生日涉及查找生日匹配的所有行今天的日期。这可以通过一个简单的 MySQL 查询来实现,该查询将存储为 UNIX 时间戳的生日与今天的日期进行比较。以下 SQL 查询将获取今天有生日的所有用户: FROM USERS ...
    编程 发布于2024-11-16
  • 如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    MySQL配置不正确:相对路径的问题在Django中运行python manage.py runserver时,可能会遇到以下错误:ImproperlyConfigured: Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-...
    编程 发布于2024-11-16
  • 为什么 Visual Studio 2010 中 x86 和 x64 的浮点运算不同?
    为什么 Visual Studio 2010 中 x86 和 x64 的浮点运算不同?
    x86 和 x64 之间的浮点算术差异在 Visual Studio 2010 中,x86 和 x64 版本之间的浮点算术存在明显差异当比较某些表达式的值时出现。这种差异体现在以下代码中:float a = 50.0f; float b = 65.0f; float c = 1.3f; float ...
    编程 发布于2024-11-15
  • 如何提高带有通配符的 MySQL LIKE 运算符的性能?
    如何提高带有通配符的 MySQL LIKE 运算符的性能?
    MySQL LIKE 运算符优化问题:使用通配符(例如 '%test% ')?答案: 是的,在查询中使用特定模式时,MySQL 可以优化 LIKE 运算符的性能。前缀通配符: 如果您的查询类似于 foo LIKE 'abc%' 或 foo LIKE 'abc�...
    编程 发布于2024-11-15
  • 如何使用 PHP 通过 POST 向外部网站发送数据?
    如何使用 PHP 通过 POST 向外部网站发送数据?
    在 PHP 中通过 POST 重定向和发送数据在 PHP 中,您可能会遇到需要将用户重定向到外部的情况网站并通过 POST 将数据传递到该网站。与 HTML 表单不同,PHP 本身并不支持此行为。GET 与 POST在 Web 开发中,有两种主要方法用于从源发送数据到目的地:GET:数据作为查询参数...
    编程 发布于2024-11-15
  • 如何使用 GCC 捕获 Linux 中的分段错误?
    如何使用 GCC 捕获 Linux 中的分段错误?
    捕获 Linux 中的分段错误问:我在第三方库中遇到分段错误,但我无法解决根本问题。是否有跨平台或特定于平台的解决方案来使用 gcc 捕获 Linux 中的这些错误?A:Linux 允许将分段错误作为异常处理。当程序遇到此类故障时,它会收到 SIGSEGV 信号。通过设置信号处理程序,您可以拦截此信...
    编程 发布于2024-11-15
  • 如何在不创建实例的情况下访问Go结构体的类型?
    如何在不创建实例的情况下访问Go结构体的类型?
    在不创建物理结构的情况下访问 Reflect.Type在 Go 中,动态加载问题的解决方案需要访问结构的类型,而无需物理创建它们。虽然现有的解决方案要求在类型注册之前创建结构体并清零,但存在一种更有效的方法。人们可以利用 reflect.TypeOf((*Struct)(nil)).Elem()手术...
    编程 发布于2024-11-15
  • Java中如何高效地将整数转换为字节数组?
    Java中如何高效地将整数转换为字节数组?
    Java 中整数到字节数组的高效转换将整数转换为字节数组可用于多种目的,例如网络传输或数据存储。有多种方法可以实现此转换。ByteBuffer 类:一种有效的方法是使用 ByteBuffer 类。 ByteBuffer 是一个存储二进制数据并提供各种操作来操纵它的缓冲区。使用 ByteBuffer ...
    编程 发布于2024-11-15
  • 如何在 Go 中按多个字段对结构体切片进行排序?
    如何在 Go 中按多个字段对结构体切片进行排序?
    按多个字段对切片对象进行排序按多个条件排序考虑以下 Parent 和 Child 结构:type Parent struct { id string children []Child } type Child struct { id string }假设我们有一个...
    编程 发布于2024-11-15
  • Qt 线程与 Python 线程:我应该在 PyQt 应用程序中使用哪个?
    Qt 线程与 Python 线程:我应该在 PyQt 应用程序中使用哪个?
    PyQt 应用程序中的线程:Qt 线程与 Python 线程寻求使用 PyQt 创建响应式 GUI 应用程序的开发人员经常遇到执行的挑战长时间运行的任务而不影响 UI 的功能。一种解决方案是使用单独的线程来完成这些任务。这就提出了是使用 Qt 线程还是原生 Python 线程模块的问题。Qt 线程提...
    编程 发布于2024-11-15

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3