You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Angular DevExtreme、Node.js、MongoDB实现带远程分组的DataGrid

实现支持远程分组的DevExtreme DataGrid(Angular + Node.js + MongoDB)

下面是完整的实现方案,涵盖前端Angular DevExtreme配置、后端Node.js+MongoDB接口开发,以及确保兼容和满足需求的关键细节:


一、后端:Node.js + MongoDB 实现远程分组接口

1. 初始化项目并安装依赖

创建Node.js项目后,安装核心依赖:

npm install express mongoose devextreme-query-mapper cors
  • devextreme-query-mapper是核心工具,负责将DevExtreme前端传入的loadOptions转换为MongoDB可识别的查询语句。

2. 定义支持全数据类型的MongoDB模型

创建models/dataItem.js,覆盖常用数据类型(字符串、数字、日期、布尔、嵌套对象、数组):

const mongoose = require('mongoose');

const dataSchema = new mongoose.Schema({
  stringField: String,
  numberField: Number,
  dateField: Date,
  booleanField: Boolean,
  objectField: {
    nestedString: String,
    nestedNumber: Number
  },
  arrayField: [String]
});

module.exports = mongoose.model('DataItem', dataSchema);

3. 编写处理远程分组的API路由

创建routes/data.js,实现接收前端loadOptions、转换查询、执行MongoDB聚合分组的逻辑:

const express = require('express');
const router = express.Router();
const DataItem = require('../models/dataItem');
const { translateLoadOptionsToMongoQuery } = require('devextreme-query-mapper');

router.post('/data', async (req, res) => {
  try {
    const loadOptions = req.body;
    // 转换前端参数为MongoDB查询结构
    const { filter, sort, group, skip, take, total } = translateLoadOptionsToMongoQuery(loadOptions);
    
    let aggregationPipeline = [];
    
    // 应用过滤条件
    if (filter) aggregationPipeline.push({ $match: filter });
    
    // 处理分组请求:按指定字段分组,保留组内所有数据
    if (group?.length) {
      const groupStages = group.map(g => ({
        $group: {
          _id: `$${g.selector}`,
          items: { $push: '$$ROOT' }
        }
      }));
      aggregationPipeline = aggregationPipeline.concat(groupStages);
    }
    
    // 应用排序规则
    if (sort?.length) {
      const sortObj = {};
      sort.forEach(s => sortObj[s.selector] = s.desc ? -1 : 1);
      aggregationPipeline.push({ $sort: sortObj });
    }
    
    // 应用分页
    if (skip) aggregationPipeline.push({ $skip: skip });
    if (take) aggregationPipeline.push({ $limit: take });
    
    // 执行查询
    const data = await DataItem.aggregate(aggregationPipeline);
    
    // 统计总条数(用于分页)
    let totalCount = 0;
    if (total) {
      const countPipeline = filter ? [{ $match: filter }, { $count: 'total' }] : [{ $count: 'total' }];
      const countResult = await DataItem.aggregate(countPipeline);
      totalCount = countResult[0]?.total || 0;
    }
    
    // 返回符合DevExtreme格式的响应
    res.json({ data, totalCount });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;

4. 启动服务器

创建server.js,连接MongoDB并启动服务:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const dataRouter = require('./routes/data');

const app = express();
app.use(cors());
app.use(express.json());

// 连接MongoDB(替换为你的数据库地址)
mongoose.connect('mongodb://localhost:27017/devextreme-demo', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB 连接成功'))
.catch(err => console.error('MongoDB 连接失败:', err));

app.use('/api', dataRouter);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`服务器运行在端口 ${PORT}`));

二、前端:Angular DevExtreme 配置DataGrid

1. 安装依赖

在Angular项目中安装DevExtreme:

npm install devextreme devextreme-angular

2. 组件逻辑:配置自定义数据源与模板

创建data-grid.component.ts,实现远程数据源加载、自定义单元格模板、过滤器格式化:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CustomStore } from 'devextreme/data/custom_store';

@Component({
  selector: 'app-data-grid',
  templateUrl: './data-grid.component.html',
  styleUrls: ['./data-grid.component.css']
})
export class DataGridComponent {
  dataSource: CustomStore;
  filterValue: any;

  constructor(private http: HttpClient) {
    this.dataSource = new CustomStore({
      load: async (loadOptions) => {
        const response = await this.http.post('http://localhost:3000/api/data', loadOptions).toPromise();
        return {
          data: (response as any).data,
          totalCount: (response as any).totalCount
        };
      },
      key: '_id'
    });
  }

  // 通用过滤器变更处理
  onFilterChanged(event: any, options: any) {
    const value = event.value === 'All' ? null : event.value;
    options.component.filter([options.dataField, '=', value]);
  }

  // 嵌套对象字段的过滤器处理
  onNestedFilterChanged(event: any, options: any) {
    const value = event.value === 'All' ? null : event.value;
    options.component.filter([`objectField.nestedString`, '=', value]);
  }
}

3. 模板:配置远程操作、自定义模板与过滤器

data-grid.component.html:

<dx-data-grid
  [dataSource]="dataSource"
  [remoteOperations]="{ grouping: true, filtering: true, sorting: true, paging: true }"
  [groupPanel]="{ visible: true }"
  [showBorders]="true"
>
  <!-- 字符串字段 -->
  <dxi-column dataField="stringField" caption="字符串字段">
    <dxo-filter-cell-template>
      <ng-template let-options>
        <dx-text-box [(value)]="filterValue" (onValueChanged)="onFilterChanged($event, options)"></dx-text-box>
      </ng-template>
    </dxo-filter-cell-template>
  </dxi-column>

  <!-- 数字字段 -->
  <dxi-column dataField="numberField" caption="数字字段" dataType="number">
    <dxo-filter-cell-template>
      <ng-template let-options>
        <dx-number-box [(value)]="filterValue" (onValueChanged)="onFilterChanged($event, options)"></dx-number-box>
      </ng-template>
    </dxo-filter-cell-template>
  </dxi-column>

  <!-- 日期字段 -->
  <dxi-column dataField="dateField" caption="日期字段" dataType="date">
    <dxo-filter-cell-template>
      <ng-template let-options>
        <dx-date-box [(value)]="filterValue" (onValueChanged)="onFilterChanged($event, options)"></dx-date-box>
      </ng-template>
    </dxo-filter-cell-template>
  </dxi-column>

  <!-- 布尔字段 -->
  <dxi-column dataField="booleanField" caption="布尔字段" dataType="boolean">
    <dxo-filter-cell-template>
      <ng-template let-options>
        <dx-select-box [items]="['All', true, false]" [(value)]="filterValue" (onValueChanged)="onFilterChanged($event, options)"></dx-select-box>
      </ng-template>
    </dxo-filter-cell-template>
  </dxi-column>

  <!-- 嵌套对象字段(自定义单元格模板) -->
  <dxi-column dataField="objectField" caption="嵌套对象">
    <dxo-cell-template>
      <ng-template let-options>
        <span>{{options.data.objectField.nestedString}} ({{options.data.objectField.nestedNumber}})</span>
      </ng-template>
    </dxo-cell-template>
    <dxo-filter-cell-template>
      <ng-template let-options>
        <dx-text-box [(value)]="filterValue" (onValueChanged)="onNestedFilterChanged($event, options)"></dx-text-box>
      </ng-template>
    </dxo-filter-cell-template>
  </dxi-column>

  <!-- 数组字段(自定义单元格模板) -->
  <dxi-column dataField="arrayField" caption="数组">
    <dxo-cell-template>
      <ng-template let-options>
        <ul style="margin:0; padding-left:1.5rem;">
          <li *ngFor="let item of options.data.arrayField">{{item}}</li>
        </ul>
      </ng-template>
    </dxo-cell-template>
  </dxi-column>

  <!-- 分页配置 -->
  <dxo-paging [pageSize]="10"></dxo-paging>
  <dxo-pager [showPageSizeSelector]="true" [allowedPageSizes]="[5,10,20]" [showInfo]="true"></dxo-pager>
</dx-data-grid>

三、确保兼容与无错误的关键细节

  1. 数据类型适配:

    • 后端模型覆盖所有MongoDB支持的数据类型,前端通过dataType属性匹配字段类型,避免格式解析错误。
    • 自定义模板针对嵌套对象、数组等复杂类型单独处理,确保渲染正常。
  2. 远程操作配置:

    • 必须显式设置remoteOperations.grouping: true,告知DataGrid所有分组逻辑在后端执行。
    • 后端使用MongoDB聚合管道处理分组,而非普通查询,确保分组结果符合DevExtreme要求。
  3. 过滤器格式化:

    • 针对不同数据类型使用对应控件(日期选择器、数字框等),避免字符串格式过滤日期/数字导致的错误。
    • 嵌套字段的过滤器需指定完整路径(如objectField.nestedString),确保后端能正确解析过滤条件。
  4. 版本兼容:

    • 保持Angular与DevExtreme版本匹配(参考DevExtreme官方版本矩阵),例如Angular 17对应DevExtreme 23.2+。
    • 使用最新稳定版的devextreme-query-mapper,修复旧版本的查询转换bug。
    • Mongoose使用6+版本,适配MongoDB 5.x/6.x服务器。
  5. 错误处理:

    • 前端CustomStore的load方法捕获请求错误,DevExtreme会自动显示错误提示。
    • 后端添加全局错误捕获,返回清晰的错误信息,便于调试。

内容的提问来源于stack exchange,提问作者Ranjith Tippani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:27:37