Dear Syncfusion Technical Support Team,
Hello!
We are encountering an issue with the Syncfusion Angular TreeGrid component (version: 30.1.37) where the dirty state of NgForm and NgModel is inconsistent when using a custom edit template with virtualization enabled (enableVirtualization = true). This issue only occurs when virtualization is enabled, and due to our application's requirement for dynamically bound columns, we cannot directly use reactive forms. We hope you can help analyze the root cause and provide a solution or best practice recommendations.
In the custom edit template (ng-template) of the TreeGrid, we use Angular's template-driven forms (NgForm and NgModel) to implement inline editing. When virtualization is enabled (enableVirtualization = true), after editing an input field (e.g., FIELD2) and triggering the blur event, the values of this.orderForm.controls[id].dirty and field.dirty are inconsistent. For example, field.dirty may be true, while this.orderForm.controls[id].dirty is false, making it impossible to accurately determine whether the user has modified the form. Disabling virtualization (enableVirtualization = false) resolves the issue, suggesting that virtualization may be the root cause. The same issue occurs with control.setErrors.
https://stackblitz.com/edit/angular-xujxcree?file=src%2Fapp.component.html,src%2Fapp.component.ts
Best regards,
Hi techlandandyzhang,
Greetings from Syncfusion Support.
We have reviewed your shared sample and successfully replicated the issue — the NgForm's dirty state is not updating correctly when virtualization is enabled (enableVirtualization = true). This occurs because TreeGrid reuses DOM elements during virtualization, which interrupts Angular's internal form tracking.
Code Snippet:
To sync the dirty state manually, use the following code inside your ngModelChange or blur event:
ts
const control = this.orderForm?.controls[id]; if (field.dirty && control && !control.dirty) { control.markAsDirty(); } |
This ensures the form control reflects the actual user interaction, even when virtualization affects DOM registration.
You can refer to the updated StackBlitz here: V4tuztrj (duplicated) - StackBlitz
Let us know if you need help generalizing this for dynamic fields or integrating with validation logic — we’re happy to assist.
Best regards,
Sreedhar Kumar
Dear Sreedhar Kumar,
Thank you for your prompt response and for successfully reproducing the issue with the Syncfusion Angular TreeGrid component. We greatly appreciate the solution you provided, which involves manually calling control.markAsDirty() in the ngModelChange or blur event.
Additionally, we have observed that when virtualization is enabled (enableVirtualization = true), the issue with form.controls[id].setErrors persists, as the error state of the form control does not consistently update. This makes it challenging to implement reliable validation logic.
Sometimes, when the current field fails validation, I need to set validation failures for other related fields as well. Therefore, I need to use form.controls[id].setErrors instead of field.control.setErrors.
we have observed that when virtualization is enabled (enableVirtualization = true), the issue with form.controls[id].setErrors persists, as the error state of the form control does not consistently update. This makes it challenging to implement reliable validation logic.
Sometimes, when the current field fails validation, I need to set validation failures for other related fields as well. Therefore, I need to use form.controls[id].setErrors instead of field.control.setErrors.
Are there any good solutions?
Thanks for your detailed analysis. Based on your observations and Syncfusion’s internal behavior with virtualization enabled in EJ2 TreeGrid, here’s a consolidated summary:
· Virtualization creates two form instances internally.
· ViewChild retrieves the first form, while the second (virtual) form is used for rendering and validation.
· This mismatch causes setErrors() to apply on the wrong form, resulting in missing error messages.
To ensure validation errors are applied correctly:
· Use ViewChildren to access the latest form instance.
· Apply validation using orderForms.last, which reliably points to the virtual form.
Code Snippet:
ts
@ViewChildren('orderForm') public orderForms!: QueryList<NgForm>;
onModelChange(value: any, field: any, id: string): void { const form = this.getActiveForm(); const control = form?.controls[id]; console.log( `ngModelChange - FormControl dirty: ${control?.dirty}, NgModel dirty: ${field?.dirty}` ); }
getActiveForm(): NgForm | undefined { return this.orderForms?.last; }
onblur(field: any, id: string): void { const form = this.getActiveForm(); const control = form?.controls[id];
if (this.orderData?.FIELD2) { const value = parseInt(this.orderData.FIELD2); setTimeout(() => { if (value > 2000) { control?.setErrors({ validError: true }); } else { control?.setErrors(null); } }, 300); } |
You can find the updated implementation sample here:
Note: We have attached a testing video with the updated sample
Let me know if you'd like to extend this to other fields or integrate additional validation rules.
Best regards,
Sreedhar Kumar Panarapu