Reputation: 385
Is there any way to force a Blazor Layout to be initialized before its component? I find it's not always consistent, depending on how/when the page is accessed. It causes issues when I've defined a CascadingParameter in the Layout which the component depends on.
Component setup:
@page "/parent1/parent2/parent3/things/1/details"
@layout MyLayout
// show thing here
@code
{
[CascadingParameter] public object Thing { get; set; } // can be null
protected override void OnInitialized()
{
// this may occur before or after the layout is initialized
// do something with Thing
}
}
Layout setup:
@inherits LayoutBase
@if (thing is not null) {
<CascadingValue Value="@thing" IsFixed="true">
@Body
</CascadingValue>
}
@code {
object thing;
protected override void OnInitialized()
{
// this may occur before or after the component is initialized
thing = GetThingFromDatabase();
// validate thing here
}
}
I thought Layouts would be the best place to guard/validate/authorize the route parameters and then store objects for any child pages but if the timing is random that doesn't work. For example, I'm after a page structure like the following.
/things/1 (layout/abstract page)
/things/1/details (page using layout)
/things/1/otherStuff (page using layout)
/things/1/moreStuff (page using layout)
I only want to load the thing once and keep it around as they navigate between the child pages.
Upvotes: 2
Views: 3579
Reputation: 30001
Is there any way to force a Blazor Layout to be initialized before its component?
You, the programmer, have no control over component instantiation. That's all handled by the Renderer.
I only want to load the thing once and keep it around as they navigate between the child pages?
Use a Scoped Service that gets and holds thing - ThingService - and inject it into whatever component needs it.
Here's some example code:
First the service. Register as Scoped Service.
namespace StackOverflow.Server
{
public class Thing
{
public string? ThisThing { get; set; }
public bool Loading => this.ThisThing is null;
public async Task GetThing()
{
// Emulated a real async database operation
await Task.Delay(500);
this.ThisThing = "Successful Database get";
}
}
}
A component to demo the cascade.
<h3>ShowThing Component</h3>
<div class="m-3">
Thing: @thing!.ThisThing
</div>
@code {
[CascadingParameter] private Thing? thing { get; set; }
}
A test page:
@page "/"
<CascadingValue Value=Thing>
<ShowThing />
</CascadingValue>
<div class="m-3">
Thing: @this.Thing.ThisThing
</div>
@code {
[Inject] Thing? Thing { get; set; }
protected async override Task OnInitializedAsync()
{
await Thing!.GetThing();
}
}
Notice that you don't need to test if thing
is not null
. I've removed the IsFixed
, you don't need it. The cascade handles nulls. When Thing
gets a real value in OnInitializedAsync
, the post OnInitializedAsync
render event detects the change in Thing
and passes the changes on to any components registered for the cascade by calling SetParametersAsync
on those components, triggering a component render.
Upvotes: 3