Skip to content

Add async cascading refresh support for DependsOn field dependencies #93

@phmatray

Description

@phmatray

Summary

The current IFieldDependency<TModel> interface only supports synchronous OnDependencyChanged() callbacks. This prevents lookup and autocomplete fields from refreshing their options asynchronously when a parent field changes (e.g., selecting a Country should trigger an async API call to fetch Cities).

This was identified as Phase C during the implementation of #27 (lookup fields).

Problem

// Current interface — synchronous only
public interface IFieldDependency<TModel>
{
    string DependentFieldName { get; }
    void OnDependencyChanged(TModel model); // ← sync
}

When a parent field changes, OnDependencyChanged is called synchronously. This means:

  • Cannot call async APIs to refresh child field options
  • Cannot show loading states during option refresh
  • Cannot cancel in-flight requests when the parent changes again

Proposed Solution

1. Add IAsyncFieldDependency<TModel> interface

public interface IAsyncFieldDependency<TModel> : IFieldDependency<TModel>
{
    Task OnDependencyChangedAsync(TModel model, CancellationToken cancellationToken = default);
}

2. Extend DependsOn() with async overload

// New async overload
.AddField(x => x.City)
    .DependsOn(x => x.Country, async (model, country, ct) =>
    {
        var cities = await cityApi.GetByCountryAsync(country, ct);
        // update options...
    })

3. Add WithCascadingFilter convenience method

.AddField(x => x.City)
    .AsAutocomplete(cityProvider)
    .WithCascadingFilter<string>(
        parentField: x => x.Country,
        filterProvider: (country, ct) => cityProvider.WithFilter("country", country))

4. Update rendering pipeline

  • FormCraftComponent must detect IAsyncFieldDependency and await the async callback
  • Show a loading indicator on the child field while options are refreshing
  • Support CancellationToken to cancel stale requests

Acceptance Criteria

  • IAsyncFieldDependency<TModel> interface added to core abstractions
  • DependsOn() has an async overload accepting Func<TModel, TDependsOn, CancellationToken, Task>
  • FormCraftComponent handles async dependency refresh with loading state
  • Stale requests are cancelled when the parent field changes again
  • Existing sync DependsOn() continues to work unchanged (backward compatible)
  • Unit tests for async dependency builder extensions
  • Integration works with AsAutocomplete() and AsLookup() from Feature Request : Lookup fields interface over cascaded drop down (e.g country and city) #27

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions