Angular10 TS中数组遍历失效、length返回0问题排查
Angular项目数组遍历异常:console可见数据但length为0
问题摘要
- 遍历业务数组时,
console.log打印数组可看到完整预期内容,HTML模板中也可正常通过*ngFor渲染数据 - TS代码中读取数组
length属性返回0,所有遍历方式(map/forEach/for...of/索引for循环)均无法正常执行 - 异常逻辑位于
saveOrigin函数内,运行环境为Angular 10旧项目,暂不升级框架版本
相关代码
数组数据组装逻辑(三层嵌套HTTP请求)
this.api.getTrucker(this.routeData[oVal], this.routeData[destVal], key).subscribe((dataTrucker: any[]) => { this.api.getAgent(this.routeData[destVal], key).subscribe((dataAgent: any[]) => { this.api.getDrop(this.routeData[oVal], key).subscribe((dataDrop: any[]) => { let dataLoop: JSONreturn = { numberOf: i, name: "Segment " + i, destination: this.routeData[destVal], destinationName: destVal, origin: this.routeData[oVal], originName: oVal, drop: this.routeData[dropVal], dropList: dataDrop, dropName: dropVal, truckerList: dataTrucker, trucker: this.routeData[truckerVal], truckerName: truckerVal, agent: this.routeData[agentVal], agentList: dataAgent, agentName: agentVal, comment: this.routeData[commentsVal], commentName: commentsVal } this.routeSegment.push(dataLoop); this.saleAgent = this.routeData.SalesAgent; this.routeSegment.sort((a, b) => a.numberOf - b.numberOf); }); }); });
对话框打开与传参逻辑
const newDialogRef: MatDialogRef<NewRouteDialogComponent> = this.dialog.open(NewRouteDialogComponent, { width: 'auto', minWidth: '85vw', minHeight: '60vh', height: 'auto', data: { origin: this.origin, dest: this.dest, routeSegmentSort: this.routeSegment, truckUnid: this.truckUnid, direction: this.direction, routeData: this.routeData, saleAgent: this.saleAgent, min: this.min, max: this.max, airList: this.airList, airListAll: this.airListAll, salesList: this.salesList, chosenAir: this.chosenAir, newOrEdit: false } }); newDialogRef.afterClosed().subscribe((res: any) => { console.log('route changes:', res); this.search('') })
对话框数据接收定义
export interface DialogData { origin: string, dest: string, routeSegmentSort: Array<JSONreturn>, truckUnid: string, direction: string, routeData: any, min: boolean, max: boolean, saleAgent: any, airList: any[], salesList: any[], airListAll: any[], chosenAir: Array<string>, newOrEdit: boolean } constructor( private api: ApiService, @Inject(MAT_DIALOG_DATA) public data: DialogData, public dialog: MatDialog, private toastrService: NbToastrService, private dialogRef: MatDialogRef<NewRouteDialogComponent> ) { }
异常触发函数
saveOrigin () { const sData = { "dbname": "data", "unid": this.data.truckUnid, "fieldname": 'Origin_1', "val": this.data.origin, "datatype": 'string' } if (this.data.newOrEdit) { this.api.fieldUpdate(sData).subscribe((data: any[]) => { console.log('fieldUpdate post response:',data); if (data['result'] == "success") { this.showToastUpdated('success', 'Created New Routing!'); } else { this.showToastError('warning', 'ERROR! NOT Updated! TRY AGAIN!'); } }); } else { console.log('route segment sort:', this.data.routeSegmentSort); try { console.log('route segment sort try:', this.data.routeSegmentSort); console.log('length:', this.data.routeSegmentSort.length) this.models = this.data.routeSegmentSort.map( (n: JSONreturn): string => (n.destination)); } catch (e) { console.error(e); } console.log('models data:', this.models) } }
异常表现
- 所有数组遍历API均无法拿到数据,手动构造索引数组遍历同样失效
- 控制台直接打印
length返回0,但展开打印的数组对象可见完整数据、长度符合预期 - 模板中
*ngFor遍历该数组可正常渲染所有列表项
根因
两个典型的前端异步逻辑问题叠加导致:
console.log对引用类型的延迟求值
浏览器控制台打印对象/数组时,不会立刻序列化对象内容,仅存储对象的内存引用。手动点击展开控制台打印结果时,浏览器才会去读取该引用当前的实际内容。代码执行到console.log行时数组确实是空的(length为0),后续嵌套HTTP请求返回、向数组push完数据后,再展开控制台看到的是更新后的数组,并非打印时刻的快照,直接造成了“打印时数组已有完整数据”的误判。- 时序错误:异步请求未完成就传参打开对话框
当前代码发起三层嵌套HTTP请求后,没有等待任何请求返回,就立刻执行dialog.open,把初始为空的this.routeSegment数组引用传入对话框。对话框初始化、ngOnInit钩子触发调用saveOrigin时,HTTP请求还未返回,数组还未被push入任何数据,因此读取length返回0、遍历无结果。
HTML模板能正常渲染数据,是Angular 变更检测 的正常表现:异步回调执行更新数组后,Angular会自动触发组件重渲染,因此页面最终能展示完整数据,但ngOnInit中写死的同步逻辑早已执行完毕,不会自动重新运行。
解决方案
方案1(推荐):修复异步时序,数据组装完成后再打开对话框
废弃三层嵌套subscribe的写法,用RxJS的forkJoin合并三个并行HTTP请求,等所有接口返回、数组完整组装排序完成后,再执行对话框打开逻辑,从根源上避免传空数组的问题:
// 顶部引入forkJoin import { forkJoin } from 'rxjs'; // 替换原来的三层嵌套请求+提前open dialog的逻辑 forkJoin({ dataTrucker: this.api.getTrucker(this.routeData[oVal], this.routeData[destVal], key), dataAgent: this.api.getAgent(this.routeData[destVal], key), dataDrop: this.api.getDrop(this.routeData[oVal], key) }).subscribe(({dataTrucker, dataAgent, dataDrop}) => { const dataLoop: JSONreturn = { numberOf: i, name: "Segment " + i, destination: this.routeData[destVal], destinationName: destVal, origin: this.routeData[oVal], originName: oVal, drop: this.routeData[dropVal], dropList: dataDrop, dropName: dropVal, truckerList: dataTrucker, trucker: this.routeData[truckerVal], truckerName: truckerVal, agent: this.routeData[agentVal], agentList: dataAgent, agentName: agentVal, comment: this.routeData[commentsVal], commentName: commentsVal } this.routeSegment.push(dataLoop); this.saleAgent = this.routeData.SalesAgent; this.routeSegment.sort((a, b) => a.numberOf - b.numberOf); // 数组组装完成后再打开对话框 const newDialogRef: MatDialogRef<NewRouteDialogComponent> = this.dialog.open(NewRouteDialogComponent, { width: 'auto', minWidth: '85vw', minHeight: '60vh', height: 'auto', data: { origin: this.origin, dest: this.dest, routeSegmentSort: [...this.routeSegment], // 传数组副本避免后续引用改动影响对话框数据 truckUnid: this.truckUnid, direction: this.direction, routeData: this.routeData, saleAgent: this.saleAgent, min: this.min, max: this.max, airList: this.airList, airListAll: this.airListAll, salesList: this.salesList, chosenAir: this.chosenAir, newOrEdit: false } }); newDialogRef.afterClosed().subscribe((res: any) => { console.log('route changes:', res); this.search('') }) })
传参时建议用[...this.routeSegment]传入数组副本,避免父组件后续改动数组引用导致对话框数据异常。
方案2(特殊业务场景使用):提前打开对话框时监听数据变化
如果业务要求必须先弹出对话框再加载数据,不要在ngOnInit中直接同步读取数组执行遍历,改为在对话框中监听传入数组的变化,等数据填充完成后再执行saveOrigin中的处理逻辑。更规范的实现是传参时传入BehaviorSubject类型的可观察对象,数据准备完成后向流中发值,对话框内订阅流即可拿到完整数据。
内容的提问来源于stack exchange,提问作者Luke Sanders
相关产品推荐
相关产品推荐

