I have created a simple Blazor application and I need to show a loading screen on the index.razor page. I am not needed to include it on the index.razor page. I am using some indirect way to do it.
First I have created a one class called Appsettings.cs and brought the loading logic inside it
Appsettings.cs
public class AppSettings { public static bool _IsProcessing { get; set; } = false; public static MainLayout _layout { get; set; } = new MainLayout(); public static void Loading(bool isProcessing) { _IsProcessing = isProcessing; if(_layout !=null) _layout .LoadingScreenShowing(_IsProcessing); } } Then my index.razor file is like this, when press the below Load button, I need to show the Loading screen.
index.razor
<button onclick="PageLoading">Load</button> @code{ protected override void Oninitialized(){} private void PageLoading(){ AppSettings.Loading(true); this.StateHasChanged(); //do something AppSettings.Loading(false); this.StateHasChanged(); } After I have included loading part into the MainLayout.razor not explicitly to the index.razor.
MainLayout.razor
@inherits LayoutComponentBase <PageTitle>BlazorApp1</PageTitle> <div> <div> <NavMenu /> </div> <main> <div> <a href="" target="_blank">About</a> </div> <article> <Loading IsVisible="@IsLoading" @ref="load"/> //loading component @Body </article> </main> </div> And I've created a partial class to place the functional part of the main layout.
MainLayout.razor.cs
public partial class MainLayout{ private bool IsLoading; Loading load ; public async void LoadingScreenShowing(bool isLoading) { load = new Loading(); IsLoading = isLoading; this.StateHasChanged();//exception is throwing from this line await Task.CompletedTask; } } When execute the this.StateHasChanged() line, I am getting the exception called
System.InvalidOperationException: 'The render handle is not yet assigned.'
Why is this occurring?
41 Answer
You can't do it the way your are trying to. Component lifecycles are managed by the Renderer: you can't create a component instance and somehow shoehorn it into a page. That's the reason for the error.
Here's a heavily refactored version of your code that I think does what intend.
The scoped service that maintains the App State (your AppSettings):
public class AppStateService { private bool _isLoaded; public event Action? LoadStateChanged; public bool IsLoaded { get => _isLoaded; set { if (_isLoaded != value) { _isLoaded = value; LoadStateChanged?.Invoke(); } } } } registered in program:
builder.Services.AddScoped<AppStateService>(); The Loading component - I've used a simple bootstrap alert to display the loading message.
@inject AppStateService appStateService @implements IDisposable @if (!this.appStateService.IsLoaded) { <div> Page Loading... </div> } @code { protected override void OnInitialized() => this.appStateService.LoadStateChanged += this.OnStateChanged; private void OnStateChanged() => this.InvokeAsync(StateHasChanged); public void Dispose() => this.appStateService.LoadStateChanged -= this.OnStateChanged; } MainLayout:
@inherits LayoutComponentBase <PageTitle>BlazorApp3</PageTitle> <div> <div> <NavMenu /> </div> <main> <div> <LoginDisplay /> <a href="" target="_blank">About</a> </div> <article> <Loading /> @Body </article> </main> </div> And finally the demo page:
@page "/" @inject AppStateService appStateService; <PageTitle>Index</PageTitle> <h1>Hello, world!</h1> Welcome to your new app. <SurveyPrompt Title="How is Blazor working for you?" /> @code{ protected async override Task OnInitializedAsync() { this.appStateService.IsLoaded = false; // emulate an async loading event await Task.Delay(2000); this.appStateService.IsLoaded = true; } }