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

Angular独立组件改造报错:_form_form_component__WEBPACK_IMPORTED_MODULE_57__未定义

Angular独立组件改造后出现TypeError: _form_form_component__WEBPACK_IMPORTED_MODULE_57__ is undefined错误

问题描述

将Angular项目中所有组件改造为独立组件(standalone)后,最后一个组件在浏览器控制台抛出TypeError: _form_form_component__WEBPACK_IMPORTED_MODULE_57__ is undefined错误。项目打包无问题,仅启动后浏览器报错,其他组件已成功改造为独立组件且运行正常。

完整控制台报错信息

Uncaught TypeError: _form_form_component__WEBPACK_IMPORTED_MODULE_57__ is undefined
    ApUiFormComponent main.js:10528
    31619 datatable-export-modal.component.ts:27
    31619 datatable-export-modal.component.ts:240
    Webpack 17
        __webpack_require__
        57317
        __webpack_require__
        14874
        __webpack_require__
        98918
        __webpack_require__
        50635
        __webpack_require__
        84429
        __webpack_require__
        __webpack_exec__
        <anonymous>
        O
        <anonymous>
        webpackJsonpCallback
        <anonymous>
main.js:10528:70
    ApUiFormComponent main.js:10528
    31619 datatable-export-modal.component.ts:27
    31619 datatable-export-modal.component.ts:240
    Webpack 17
        __webpack_require__
        57317
        __webpack_require__
        14874
        __webpack_require__
        98918
        __webpack_require__
        50635
        __webpack_require__
        84429
        __webpack_require__
        __webpack_exec__
        <anonym>
        O
        <anonym>
        webpackJsonpCallback
        <anonym>

组件代码

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Validators } from '@angular/forms';
import { DownloadService, DynatraceService } from '@enercity/ap-core';
import { CellValue, Column, Workbook } from 'exceljs';
import { cloneDeep } from 'lodash-es';
import {
  ApUiButtonComponent,
  ApUiButtonsComponent,
  ApUiFormComponent,
  ApUiModalComponent,
  FormConfig,
} from '../../components';
import { ToastService } from '../../services/toast.service';
import { ApUiDatatableComponent } from '../datatable/datatable.component';

interface FormValue {
  fileType: 'excel' | 'csv';
  rowCount: number;
  columnIds: string[];
}

@Component({
  selector: 'ui-datatable-export-modal',
  templateUrl: './datatable-export-modal.component.html',
  styleUrls: ['./datatable-export-modal.component.scss'],
  standalone: true,
  imports: [ApUiButtonComponent, ApUiButtonsComponent, ApUiFormComponent, ApUiModalComponent],
})
export class ApUiDatatableExportModalComponent<Row, Filter, SubtableRow> implements OnInit {
  @Input() public datatable!: ApUiDatatableComponent<Row, Filter, SubtableRow>;

  @ViewChild('form') public form?: ApUiFormComponent<FormValue>;
  @ViewChild('modal') public modal?: ApUiModalComponent;

  public initialValue: Partial<FormValue> = {};
  public loading = false;
  public formConfig?: FormConfig<FormValue>;

  constructor(
    private downloadService: DownloadService,
    private toast: ToastService,
    private dynatrace: DynatraceService,
  ) {}

  public ngOnInit(): void {
    if (!this.datatable) {
      throw new Error('DatatableExportModalComponent: invalid input - datatable is missing');
    }
    const visibility = this.datatable.columnVisibility.state;
    this.initialValue = {
      columnIds:
        this.datatable.config?.columns
          .map((column, index) => {
            if (visibility.includes(column.property)) {
              return index.toString();
            }
            return 'invisible';
          })
          .filter((item) => item !== 'invisible') ?? [],
      fileType: 'excel',
      rowCount: 1000,
    };
    this.formConfig = {
      sections: [
        {
          description: this.datatable.i18n.texts.exportingModalFormDescription,
          rows: [
            {
              controls: [
                {
                  type: 'RADIOS',
                  property: 'fileType',
                  trackId: `${this.datatable.config?.uniqueId}-file-type`,
                  texts: {
                    label: this.datatable.i18n.texts.exportingModalFiletypeLabel,
                  },
                  validators: [Validators.required],
                  orientation: 'VERTICAL',
                  options: [
                    {
                      value: 'excel',
                      text: this.datatable.i18n.texts.exportingModalExcelOptionText,
                    },
                    {
                      value: 'csv',
                      text: this.datatable.i18n.texts.exportingModalCSVOptionText,
                    },
                  ],
                },
              ],
            },
            {
              controls: [
                {
                  type: 'NUMBERFIELD',
                  property: 'rowCount',
                  trackId: `${this.datatable.config?.uniqueId}-row-count`,
                  texts: {
                    label: this.datatable.i18n.texts.exportingModalRowCountLabel,
                  },
                  validators: [Validators.required, Validators.min(0), Validators.max(10000)],
                  min: 0,
                  max: 10000,
                  step: 1000,
                },
              ],
            },
            {
              controls: [
                {
                  type: 'CHECKBOXES',
                  viewType: 'SWITCH',
                  property: 'columnIds',
                  trackId: `${this.datatable.config?.uniqueId}-column-ids`,
                  orientation: 'VERTICAL',
                  allOptionEnabled: true,
                  texts: {
                    label: this.datatable.i18n.texts.exportingModalVisibleColumnsLabel,
                  },
                  options: this.datatable.viewParser.headings,
                },
              ],
            },
          ],
        },
      ],
    };
  }

  public async export(value: Partial<FormValue>): Promise<void> {
    if (this.datatable.config) {
      try {
        let rows: (string | number)[][];
        if (
          this.datatable.settings.features.serverSideProcessing &&
          value.rowCount &&
          value.fileType
        ) {
          this.loading = true;
          const params = cloneDeep(this.datatable.serverSide.params);
          if (params.pagination) {
            params.pagination.limit = value.rowCount;
            params.pagination.offset = 0;
          }
          const result = await this.datatable.serverSide.performFetch(
            params,
            this.datatable.filter.value,
          );
          rows = this.datatable.dataitemService.transformMultiConfigsAndValues(
            result.data,
            this.datatable.config.columns,
          );
        } else {
          rows = this.datatable.dataitemService.transformMultiConfigsAndValues(
            this.datatable.data,
            this.datatable.config.columns,
          );
        }
        if (value.rowCount && rows.length > value.rowCount) {
          rows.length = value.rowCount;
        }
        this.processViewAppearances(rows);
        rows = rows.map((row) =>
          row.filter((item, index) => value.columnIds?.includes(index.toString())),
        );
        const headings = this.datatable.viewParser.headings
          .map((item) => item.text)
          .filter((item, index) => value.columnIds?.includes(index.toString()));
        if (value.fileType === 'csv') {
          this.exportCSV(rows, headings);
        } else if (value.fileType === 'excel') {
          await this.exportExcel(rows, headings);
        }
      } catch {
        this.dynatrace.reportError(
          `ap-ui: Datatable ${this.datatable.config?.uniqueId} - error during ${value.fileType} export`,
        );
        this.toast.showError({
          message: this.datatable.i18n.texts.exportingModalErrorToastMessage,
        });
      }
      this.modal?.close();
      this.loading = false;
    }
  }

  private processViewAppearances(rows: (string | number)[][]): void {
    if (this.datatable.config) {
      const columns = this.datatable.config.columns;
      for (const row of rows) {
        for (let i = 0; i < row.length; i++) {
          const viewConfig = columns[i].view;
          if (viewConfig?.type === 'COUNT') {
            const count = this.sanitizedCount(row[i]);
            row[i] = `${count} ${count === 1
              ? this.datatable.translateOrNotPipe.transform(
                  viewConfig.texts.singularKey,
                  viewConfig.texts.singular,
                )
              : this.datatable.translateOrNotPipe.transform(
                  viewConfig.texts.pluralKey,
                  viewConfig.texts.plural,
                )}`;
          }
        }
      }
    }
  }

  private sanitizedCount(value: string | number): number {
    if (typeof value === 'string') {
      const parsed = parseInt(value, 10);
      if (!isNaN(parsed)) return parsed;
    }
    if (typeof value === 'number') {
      if (!isNaN(value)) return value;
    }
    return 0;
  }

  private exportCSV(rows: (string | number)[][], headings: string[]): void {
    const content = `"${headings.join('","')}"\n`.concat(
      rows.map((row) => `"${row.join('","')}"`).join('\n'),
    );
    this.downloadService.download(content, `download.csv`);
  }

  private async exportExcel(rows: (string | number)[][], headings: string[]): Promise<void> {
    const workbook = this.createWorkbook(rows, headings);

    // Write workbook data to buffer
    const buffer = await workbook.xlsx.writeBuffer();

    // Trigger download of the Excel file
    this.downloadService.downloadBlob(new Blob([buffer]), 'download.xlsx');
  }

  private createWorkbook(rows: (string | number)[][], headings: string[]): Workbook {
    const workbook = new Workbook();
    const sheet = workbook.addWorksheet('Download');
    sheet.columns = headings.map((item) => ({ header: item }));
    for (const row of rows) {
      sheet.addRow(row);
    }

    // Iterate over each column to set data type for numeric cells (exceljs sees numbers as strings)
    sheet.columns.forEach((column: Partial<Column>, index: number) => {
      const columnValues = column.values;
      if (columnValues) {
        columnValues.forEach((value: CellValue, rowIndex: number): void => {
          const cell = sheet.getCell(rowIndex + 1, index + 1);
          if (cell.value != null && cell.value !== '') {
            const cellValue = cell.value.toString();

            // Check if the value has leading zeros
            const hasLeadingZeros = /^0\d+/.test(cellValue);

            // Check if the value is large enough to be written in scientific notation
            const needsScientificNotation = !isNaN(Number(cellValue)) && Number(cellValue) >= 1e15;

            if (hasLeadingZeros || needsScientificNotation) {
              cell.value = cellValue;
            } else if (!isNaN(Number(cellValue))) {
              cell.value = Number(cellValue);
              if (typeof cell.value === 'number') {
                cell.numFmt = '0';
              }
            }
          }
        });
      }
    });

    // Dynamically adjust column widths based on content length
    sheet.columns.forEach((column: Partial<Column>) => {
      if (column.values) {
        const lengths =
          column.values.map((v: CellValue) => {
            const typedV = v as string | number;
            return typedV?.toString().length ?? 0;
          }) ?? [];
        const maxLength = Math.max(...lengths.filter((v: number) => typeof v === 'number'));
        column.width = maxLength;
      }
    });

    return workbook;
  }
}

解决方案

  • 确认ApUiFormComponent已转为独立组件:检查ApUiFormComponent的元数据,确保设置了standalone: true,并且它自身的依赖(如表单模块、其他子组件)已在imports数组中正确导入。
  • 验证导入路径与导出:检查../../components路径下的ApUiFormComponent是否正确导出,确认导入语句中组件名称拼写无误,文件路径未发生变更。
  • 清理缓存并重建:执行ng clean清除Angular缓存,再重新构建项目,避免Webpack缓存导致的旧模块引用问题。
  • 排查循环依赖:检查ApUiFormComponent与当前组件是否存在循环导入的情况,循环依赖会导致模块加载异常,需调整组件结构消除循环。
  • 检查ApUiFormComponent的依赖:如果ApUiFormComponent尚未转为独立组件,需确保它所在的模块已在当前组件的imports中导入(但更推荐将其转为独立组件以匹配项目改造方向)。

内容的提问来源于stack exchange,提问作者David Moll

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 15:20:53