Search code examples
timerblazorwebassembly

Blazor Webassembly - Why does function get called on TimerOnElapsed?


I am playing with the Blazor Webassembly template, specifically the weather forecast page.

I simply add a timer and have a function that increments a variable, that is shown on the page. What I don't get is, the addText function is never called specifically, and yet the variable keeps incrementing with the timer elapsed. So I don't understand what is happening here.

Could someone enlighten me? Do all functions within the html area get called on StateHasChanged() or something? If I changed the line in the html to '@((MarkupString)sometext.ToString())', it doesn't increment. Here is the code:

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
    <br />

    @((MarkupString)addText())
}

@code {
    private WeatherForecast[] forecasts;

    private int sometext = 0;
    Timer timer;

    protected override async Task OnInitializedAsync()
    {
        timer = new Timer();
        timer.Interval = 1000;
        timer.Elapsed += TimerOnElapsed;

        timer.Start();

        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

    private void TimerOnElapsed(object sender, ElapsedEventArgs e)
    {
        StateHasChanged();
    }

    private string addText()
    {
        return sometext++.ToString();
    }
}


Solution

  • StateHaseChanged is forcing a render. The render is calling the function... The Markup area is compiled to a delegate which is called on render. Your function is just a subroutine call. You are counting the render calls.

    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from the server.</p>
    
    @if (forecasts == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var forecast in forecasts)
                {
                    <tr>
                        <td>@forecast.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                    </tr>
                }
            </tbody>
        </table>
        <br />
    
        @sometext 
    }
    
    @code {
        private WeatherForecast[] forecasts;
    
        private int sometext = 0;
        Timer timer;
    
        protected override async Task OnInitializedAsync()
        {
            timer = new Timer();
            timer.Interval = 1000;
            timer.Elapsed += TimerOnElapsed;
    
            timer.Start();
    
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
    
        private void TimerOnElapsed(object sender, ElapsedEventArgs e)
        {
            sometext++;
            StateHasChanged(); // <- probably not needed not sure
        }   
    }