WaitsAtWork
WaitsAtWork

Reputation: 385

Blazor Layout with CascadingParameter initialized after component

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

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

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

Related Questions