The UI Razor files are re-organised into the following structure:

All code files, including the razor files, are explicitly namespaced to Blazr.Template.UI.
Counter.razor:
@page "/counter" @namespace Blazr.Template.UI <h1>Counter</h1> <p>Current count: @currentCount</p>
Components are the heart of the Blazor UI. Three terms are used for different component types:
Index, Counter and FetchData are all Routes.LoadingControl displays a loading message or a spinner when the page is loading it's data, and only displays the data when it's loaded. It displays a animated spinner while IsLoaded is false and only displays ChildContent when IsLoaded is true. It removes the need for a lot of error checking code in components.
PagingControl displays a paging bar. It's simplistic, designed to demo component interaction.
There are a few key concepts it does implement:
pageno to the page loop variable, and then using the local variable in any loop code. If you use page in onclick, when the click event occurs the looping has completed and page is set at _pagingData.LastPage plus one.@for (int page = 0; page <= _pagingData.LastPage; page++) { var pageno = page; var viewpageno = page + 1; <button type="button" class="btn @GetCss(pageno)" @onclick="() => this.GotToPage(pageno)">@viewpageno</button> }
Implementing IDisposable in a component, and handling events.
Re-rendering on an event. This one is conditional. If the control triggered the page change event then _currentpage == _pagingData.Page and nothing happens. this.InvokeAsync(this.StateHasChanged); isn't strictly needed in this instance, but if the event is triggered outside UI thread, this ensures thread safety - InvokeAsync is a ComponentBase method that invokes the action on the UI Thread.
private void OnPageChanged(object? sender, EventArgs e) { if (_currentpage != _pagingData.Page) { _currentpage = _pagingData.Page; this.InvokeAsync(this.StateHasChanged); } }
SetParametersAsync. if you implement SetParametersAsync you must set the component parameters immediately as shown, and then call the base SetParametersAsync with an empty ParameterView.public override Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); if (PagingData is null) throw new ApplicationException("Paging Control must have a PagingData object assigned"); return base.SetParametersAsync(ParameterView.Empty); }
PagingData will throw warnings. To prevent this a local global variable _pagingData is used throughout the component. It's mapped to PagingData using a null forgiving assignment.
private PagingData _pagingData => PagingData!;
WeatherForecastList.razor is a re-usable component containing all the code from FetchData. The component:
LoadingControl tied to the View Service IsLoaded.It's isn't obvious from WeatherForecastList how it updates when a new page is clicked in the PagingControl.

Clicking a page button Sets the new page number in PagingData and triggers PagingData.PageChanged. This triggers WeatherForecastViewService.OnPageChanged event handler that gets the new paged dataset. This triggers the WeatherForecastViewService.ListChanged event. WeatherForecastList is register for this event. It triggers StateHasChanged which re-renders the component.
Meanwhile in PagingControl, which triggered the whole sequence, the new page number has been set in PagingData before the event handler completes and calls StateHasChanged.
On first render PagingControl is inside LoadingControl, so is only rendered when loading is complete.
Note the page number is maintained across page navigations. You can go to Counter and back to FetchData. However, if you refresh the SPA then the state is lost.
Apps contains the root application component App. It's vanilla and namespaced.
@namespace Blazr.Template.UI <Router AppAssembly="@typeof(App).Assembly" PreferExactMatches="@true"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router>