简体   繁体   中英

How to preserve component instances in Blazor

I have a similar component and I whish to save the instance of the InnerComponent the first time it is rendered and render the same instance every time without reinstanceiating it.

@if(isVisible)
{
    <InnerComponent @ref="@_InnerComponent" @key="@("InnerComponentKey")">
        @ChildContent
    </InnerComponent>
}

@code{
    [Parameter] public InnerComponent _InnerComponent { get; set; }
    private bool IsVisible { get; set; }
}

When the inner component is visible the user can manipulate its state. But if IsVisible gets set to false and then true again, the inner component gets rerendered overriding _InnerComponent and thus we lose track of the changes that the user made to that InnerComponent instance.

Adding @key does not seem to help preserve the instance either. It just gets rerendered and overwritten:/ I am sure, that I am feeding it the same key both times it gets rendered, but I don't know how to inspect the keys that it gets compared to.

If it is possible to render a component instance I could do something like the following, but I can't seem to find a way to do that.

@if(isVisible)
{
    @if(_InnerComponent == null)
    {
        <InnerComponent @ref="@_InnerComponent" @key="@("InnerComponentKey")">
            @ChildContent
        </InnerComponent>
    }
    else
    {
        @_InnerComponent.Render()
    }
}

I am taking criticism on my question since I haven't asked many:)

Thanks in advance!

Simplified example:

Let's say we have the following component that I am going to call `CounterContainer`, where `&ltCounter>` is the counter component from the default Blazor project template.
 @if(CounterIsVisible) { <Counter @ref="@_Counter" @key="@("CounterKey")" /> } <button @onclick="() => CounterIsVisible =;CounterIsVisible"> Show/Hide counter </button> @code{ [Parameter] public Counter _Counter { get; set; } private bool CounterIsVisible { get; set; } = true; }

I want to save the _Counter instance, so I can see the correct _Counter.currentCount that I counted to. I could save it using a method from this article , but I find them all unpractical, because

  • the component I am building has much more data than just a single variable
  • I need the data only as long as the CounterContainer exists and only for visualisation
  • It is just too complicated for my use case

I already have the Counter reference stored. I just want to view it instead of reinstanceiating it and overwriting the whole thing.

Hope that made it a bit clearer (:

I'm surprised it compiles, since you've misspelled your component class:

[Parameter] public **InnerCopmonent** _InnerComponent { get; set; }

Why do you have an @ref to your InnerComponent at all? What does InnerComponent do, and why do you want to reference it? Can you share that component's code?

  • best option: separate View and Data. The state you want to preserve should not be kept inside the component but in a separate (ViewModel) object.

  • short solution: always render the component. Use @isVisible to turn a css-class on/off to hide it.

The way I like to do this kind of thing is to create a carrying class which I instantiate in the parent and pass to the child as a Parameter . Since a class is passed by reference, I don't need to worry about events or anything like that. (I just made up a class for demo to show you can have whatever you want in it)

CounterData.cs

public class CounterData
    {
        public int counter { get; set; }
        public string Title { get; set; } = "Counter";
        public List<int> CounterHistory { get; set; } = new List<int>();
    }

CounterContainer.razor

@page "/counter"

<h3>Counter Container</h3>

<button @onclick="()=> ShowCounter=!ShowCounter">Toggle</button>

@if (ShowCounter)
{
    <Counter CarryingInstance="PersistentData" />
}

@code {
    CounterData PersistentData = new CounterData();
    bool ShowCounter = false;
}

Counter.razor

<h3>@CarryingInstance.Title</h3>

<input @bind="CarryingInstance.Title" /><br />
<button @onclick="()=> Increment(1)">Add</button>
<button @onclick="()=>Increment(-1)">Sub</button>
@CarryingInstance.counter <br />


History:

@foreach (var item in CarryingInstance.CounterHistory)
{
    <span>&nbsp; @item</span>
}

@code {
    [Parameter]
    public CounterData CarryingInstance { get; set; }

    void Increment (int amount)
    {
        CarryingInstance.counter += amount;
        CarryingInstance.CounterHistory.Add(CarryingInstance.counter);
    }
}

I've seen the option of hiding the component with CSS and the unexpected problems it can cause because components then aren't going through normal life cycle events when they are legitimately rerendered/reinstantiated. So I'm busy updating several components to fix that exact thing. My advice would be to avoid doing that if possible.

Another way, similar to BennyBoys carrying instance method, is you could probably do it is by utilizing dependency injection and registering a type as a scoped or singleton service and having it injected into the component.

You can read here on Chris Sainty's blog about how the different registrations work in blazor and their behaviour and if it'll fit your needs.

https://chrissainty.com/service-lifetimes-in-blazor/

I would double check via official docs that these still behave the same way though just in case but it might be a nice way to do it. I think it depends on what your actual use case is whether it's a good idea to do it like this, but it's another option:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM