Table of Contents

    Book an Appointment

    How Do Form.io Choices.js Dropdowns Behave in Multi-Tab Wizards?

    While working on a clinical assessment module for a Healthcare SaaS platform, we encountered a subtle but critical UI bug. The system was built using an Angular frontend, backed by an ASP.NET Zero framework and utilized Form.io to handle highly dynamic, multi-tab wizard forms.

    During testing, we realized that a specific dropdown field representing the patient’s Clinical Stage was failing to display its pre-selected value on initial load. The underlying model correctly held the value (for instance, the stage ID was stored as 2), but the Choices.js dropdown rendered completely empty. Strangely, the moment a user clicked on the empty dropdown, the internal component state seemed to refresh and the correct value instantly appeared.

    In a healthcare environment where practitioners need immediate, accurate visibility into patient data, relying on an accidental click to reveal a selected value is unacceptable. This challenge inspired this deep dive into how Form.io processes asynchronous custom data sources, why race conditions occur with Choices.js widgets and how teams can solve this without resorting to fragile frontend hacks.

    Why Do Dropdowns Fail to Display Pre-Selected Values in Asynchronous Forms?

    The problem surfaced within a multi-tab wizard configuration where the exact same clinical stage field existed across different tabs. The application relied on ASP.NET Zero APIs to fetch assessment data and dynamic field options concurrently.

    The dropdown was configured to use a custom data source in its schema, pointing directly to a local Angular component variable. The goal was to retrieve a list of stages from the backend, map them to an array and bind them to the Form.io schema before the user interacted with the wizard.

    However, modern enterprise applications rarely load synchronously. When organizations hire angular developers for complex ui architecture, a key expectation is managing these asynchronous data flows cleanly. In this scenario, the asynchronous nature of the API call was colliding with Form.io’s aggressive rendering lifecycle.

    What Were the Symptoms of the Form.io Synchronization Issue?

    The symptoms were highly specific and consistently reproducible. We observed the following behaviors in our browser console and UI:

    • The API response successfully returned the patient data model, including the correct clinical stage ID.
    • The API response successfully returned the dropdown options, which were mapped into the Angular component array.
    • The Form.io component rendered the UI before the dropdown options were fully populated in the local array.
    • Because the schema used a custom JavaScript evaluation to pull data from the Angular component, Form.io evaluated the script exactly once during initialization.
    • When the async options finally arrived milliseconds later, Form.io did not automatically re-evaluate the custom script to rebind the selected ID to the newly available text labels.
    • Clicking the dropdown forced Choices.js to re-calculate its internal state, accidentally discovering the data and fixing the display.

    The root cause was a classic race condition: the form component was initializing and attempting to map a selected ID to a textual label before the lookup dictionary (the dropdown options) actually existed.

    How Did We Troubleshoot the Choices.js Rendering Bug?

    We evaluated several approaches to synchronize the asynchronous options data with the Form.io rendering engine.

    Can Awaiting the Promise Solve the Render Timing?

    Our first attempt involved adding an asynchronous wait to the API call before passing the data to the wizard. We wrapped the data fetch in a Promise and used the await keyword before pushing the form model to the component. While this guaranteed the data was in memory, Form.io’s internal lifecycle still initialized the Choices.js widget based on the initial schema binding, missing the update cycle.

    Does Modifying the Schema to Lazy Load Prevent the Issue?

    Next, we modified the JSON schema of the Form.io component, setting lazy loading parameters to false and attempting to switch the data source from a custom script to a raw array. This improved performance slightly but did not solve the issue of the selected value failing to bind to the textual label upon the first load, especially across multiple wizard tabs sharing the same key.

    Is Forcing a Timeout a Viable Workaround?

    We also considered using a timeout function to trigger a manual form submission update after a set delay. By calling the internal submission setter inside a timeout, the form would forcefully re-render. However, this caused an unacceptable UI flicker. As a general rule, when you hire asp.net developers for enterprise platforms, injecting arbitrary timeouts to solve race conditions is considered an anti-pattern. We needed a deterministic solution.

    Can We Conditionally Render the Form Element?

    We realized that the most robust way to ensure synchronization was to prevent Form.io from rendering entirely until all foundational data was fully resolved and mapped. By controlling the DOM attachment of the Form.io component, we could guarantee the schema had everything it needed before initialization.

    How to Ensure Form.io Rebinds Async Data Without User Interaction?

    The final implementation required decoupling the form rendering from the Angular component initialization. Instead of allowing Form.io to evaluate a custom JavaScript snippet asynchronously, we fetched the data, injected it directly into the schema definition and then authorized the form to render.

    First, we updated the schema to utilize a raw data array rather than a custom Angular binding script. This removes the dependency on Form.io’s JavaScript evaluator.

    {
      "label": "Clinical Stage",
      "widget": "choicesjs",
      "dataSrc": "values",
      "data": {
        "values": []
      },
      "valueProperty": "clinicalStageId",
      "template": "{{ item.text }}",
      "key": "patientAssessment.clinicalStageId",
      "type": "select"
    }
    

    Next, we structured our Angular component to resolve all necessary lookup data before assembling the form data wrapper. We introduced a state flag to manage the DOM rendering.

    isFormReady: boolean = false;
    clinicalWizardData: any;
    wizardSchema: any;
    ngOnInit() {
      this.initializeWizard();
    }
    async initializeWizard() {
      try {
        // 1. Fetch dropdown options fully before rendering
        const stageValues = await this.getClinicalStagesValues();
        
        // 2. Inject options directly into the schema definition
        this.wizardSchema = this.getWizardSchemaTemplate();
        this.injectDropdownOptions(this.wizardSchema, stageValues);
        // 3. Fetch the patient data model
        const wizardFormData = await this._patientAssessment.getWizardData(...).toPromise();
        // 4. Assemble the data wrapper
        const dataWrapper: any = {
          data: JSON.parse(JSON.stringify(wizardFormData))
        };
        this.clinicalWizardData = dataWrapper;
        // 5. Signal Angular to render the Form.io component
        this.isFormReady = true;
      } catch (error) {
        this.handleError(error);
      }
    }
    

    Finally, we wrapped the Form.io component in our HTML template with an structural directive.

    <ng-container *ngIf="isFormReady">
      <formio 
        [form]="wizardSchema" 
        [submission]="clinicalWizardData">
      </formio>
    </ng-container>
    

    By enforcing this strict initialization sequence, Choices.js received the full dictionary of dropdown options at the exact moment it received the selected value ID. The binding succeeded on the first pass, the field populated correctly and the cross-tab shared keys maintained their synchronization.

    What Are the Key Takeaways for Building Dynamic Forms in Angular?

    Solving this Form.io rendering issue highlighted several critical practices for managing complex UI states.

    • Avoid Custom JS Evaluations in Schemas: Relying on Form.io’s custom data source evaluation to read Angular component variables breaks the natural Angular change detection cycle. Inject data into the schema explicitly.
    • Control the Render Lifecycle: Use structural directives to prevent complex UI components from attaching to the DOM before their prerequisite data dependencies are fully resolved.
    • Reject Timeout Hacks: If you find yourself using a timeout to force a UI refresh, you are likely fighting a race condition. Identify the true async boundary and resolve it deterministically.
    • Understand Third-Party Widget Constraints: Widgets like Choices.js construct their own internal DOM. If they are initialized with empty data, modifying the underlying array later may not automatically trigger the widget to rebuild its DOM.
    • Isolate Data Fetching: When dealing with multi-tab wizards, ensure that reference data (like dropdown options) is fetched once globally, rather than allowing each tab to attempt an independent async fetch.

    How Can Expert Angular Teams Help You Scale Dynamic UI Forms?

    Dynamic, schema-driven forms are incredibly powerful for enterprise applications, but they introduce complex lifecycle and synchronization challenges. When an architecture relies on integrating third-party libraries like Form.io and Choices.js within strict Angular and ASP.NET Zero environments, understanding the deep mechanics of data binding is essential.

    When you hire ui developers for custom integrations, it is crucial that they possess the maturity to identify root-cause race conditions rather than applying superficial fixes. Proper state management and deterministic rendering ensure that platforms remain performant and reliable for end-users. If your team is navigating complex frontend architectures or needs to scale an enterprise platform, contact us to explore how our pre-vetted engineers can accelerate your roadmap.

    Social Hashtags

    #Formio #ChoicesJS #Angular #AngularDevelopment #WebDevelopment #FrontendDevelopment #JavaScript #TypeScript #DynamicForms #AsyncProgramming #SoftwareDevelopment #SaaSDevelopment #HealthcareSaaS #ASPNetZero #DeveloperTips

     

    Frequently Asked Questions

    Success Stories That Inspire

    See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.