From 5d1586fc4e07837b8223461e0a3df1fc38ef5d5d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 2 Apr 2026 09:21:00 +0300 Subject: [PATCH] fix(iot-hub): resolve ExpressionChangedAfterItHasBeenCheckedError in device install dialog Inject ChangeDetectorRef and call detectChanges() after async state mutations (entity step status changes, stepper auto-advance, ngOnInit completion). The NG0100 error occurred because async operations (setTimeout, HTTP calls) mutated component state outside Angular's change detection cycle. --- .../device-install-dialog.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts index e0fc110045..22939f0614 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/device-install-dialog/device-install-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnInit, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { MatStepper } from '@angular/material/stepper'; @@ -98,6 +98,7 @@ export class TbDeviceInstallDialogComponent implements OnInit { constructor( @Inject(MAT_DIALOG_DATA) public data: DeviceInstallDialogData, private dialogRef: MatDialogRef, + private cdr: ChangeDetectorRef, private deviceProfileService: DeviceProfileService, private deviceService: DeviceService, private dashboardService: DashboardService, @@ -129,6 +130,7 @@ export class TbDeviceInstallDialogComponent implements OnInit { console.error('Failed to parse device package ZIP', e); } this.loading = false; + this.cdr.detectChanges(); } // --- Connectivity --- @@ -357,6 +359,7 @@ export class TbDeviceInstallDialogComponent implements OnInit { } ep.status = 'running'; ep.errorMessage = null; + this.cdr.detectChanges(); try { const startTime = Date.now(); const output = await this.createEntity(ep.step); @@ -384,10 +387,12 @@ export class TbDeviceInstallDialogComponent implements OnInit { remaining.resolvedName = this.resolveVariables(remaining.step.name); } } + this.cdr.detectChanges(); } catch (err: any) { ep.status = 'error'; ep.errorMessage = err?.error?.message || err?.message || 'Unknown error'; ws.progressError = ep.errorMessage; + this.cdr.detectChanges(); return; } } @@ -414,6 +419,7 @@ export class TbDeviceInstallDialogComponent implements OnInit { if (!this.isLastWizardStep) { await this.delay(500); this.stepper.next(); + this.cdr.detectChanges(); this.onStepActivated(); } }