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
相关产品推荐
相关产品推荐

