Search code examples
c#mvvmblazorobservablecollection.net-maui

How to update blazor view observable collection mvvm .net maui


I've got a collection that takes a little while to populate so I want to show the other data on the screen first and then load in that collection incrementally (I do this with ajax and partials on the web but that doesn't work in a .net maui app).

//View:
<div class="container">
    <p>@TestMessage</p>
    @if (MyListOfItems != null)
    {
        @foreach (var item in MyListOfItems)
        {
           //display component
        }
    }
    else
    {
        //loading placeholder
    }
    //rest of the view
</div>


//Code
@code{
private ObservableCollection<ListItemVM> MyListOfItems;
private string TestMessage;


protected override async Task OnInitializedAsync()
{
    //Load the critical elements
    //If I load the collection here then it takes too long
}

//This get's called after the OnInitialisedAsync 
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        TestMessage = "testing";

        MyListOfItems = new ObservableCollection<ListItemVM>();
        //I just want to load 10 elements initially
        @for (int i = 0; i < 10; i++)
        {
            var newItem = someService.GetItemData();
            //I was hoping at this point the view would update with the new item
            MyListOfItems.Add(newItem); 
        }
    }
}
}

TestMessage updates but the collection doesn't!? But if I put TestMessage = "testing"; after the for loop then TestMessage doesn't get updated.

I've tried only updating the collection after the loop is finished like so but that doesn't work either. And also wouldn't give the nice desired incremental loading effect I'm looking for.

var itemList = new ObservableCollection<ListItemVM>();    
@for (int i = 0; i < 10; i++)
{
    var newItem = someService.GetItemData();
    itemList.Add(newItem); 
}
MyListOfItems = itemList;

What am I doing wrong? Do I need to do something like this to explicitly bind it??

@foreach (var item in Bind(x => x.MyListOfItems))

Since in a xaml view you would do this

<CollectionView ItemsSource="{Binding MyListOfItems}" />

Solution

  • Here's a very basic Razor routed component that demonstrates how to update the UI incrementally as a "Data Set" loads. I've kept it as simple as possible as you haven't provided enough detail for me to build a working model based on your code :-).

    @page "/"
    @using System.Diagnostics;
    @using System.Text
    @implements IDisposable
    
    <h1>Slow Loading Record Set</h1>
    
    @foreach (var message in this.Messages)
    {
        <div class="bg-dark text-white m-1 p-1">
            @message
        </div>
    }
    
    @code {
        private List<string> Messages = new List<string>();
        private bool cancel;
    
        protected async override Task OnInitializedAsync()
        {
            this.Messages.Add($"Started Loading at {DateTime.Now.ToLongTimeString()}");
            await LoadingSlowRecords();
            this.Messages.Add($"Completed Loading at {DateTime.Now.ToLongTimeString()}");
        }
    
        private async Task LoadingSlowRecords()
        {
            for (var counter = 0; counter < 6; counter++)
            {
                if (cancel)
                    break;
    
                await Task.Delay(3000);
                this.Messages.Add($"New Record loaded at {DateTime.Now.ToLongTimeString()}");
                Debug.WriteLine($"New Record loaded at {DateTime.Now.ToLongTimeString()}");
                StateHasChanged();
            }
        }
    
        public void Dispose()
        {
            Debug.WriteLine($"Dispose called at {DateTime.Now.ToLongTimeString()}");
            cancel = true;
        }
    }