Reputation: 1989
I have a Blazor app with many components, and nested components that I would like to share data among, but I cannot figure out how to do this efficiently. Here is, sort of, what I have:
MyProject.Pages.Index.razor:
@page "/"
<div>
@if(some_state) {
<ComponentAlpha @bind-something=Something />
else if(some_other_state){
<ComponentBeta Something="Something" />
} else {
<ComponentGamma Something="Something" />
}
</div>
@code {
String Something { get; set; }
}
MyProject.Shared.ComponentAlpha.razor:
<div>
Stuff here ...
</div>
@code {
[Parameter]
public String something { get; set; }
[Parameter]
public EventCallback<String> somethingChanged { get; set; }
private async Task MyTask() {
await somethingChanged.InvokeAsync(something);
}
}
This all works fantastic for getting data from ComponentAlpha.razor
back to Index.razor
, and from Index.razor
to ComponentBeta.razor
and ComponentGamma.razor
. My question comes in for beyond ComponentBeta.razor
and ComponentGamma.razor
.
MyProject.Shared.ComponentBeta.razor:
<div>
Stuff here ...
<ComponentDelta />
<ComponentEpsilon />
</div>
@code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentGamma.razor:
<div>
Stuff here ...
<ComponentZeta />
<ComponentEta />
</div>
@code {
[Parameter]
public String Something { get; set; }
}
MyProject.Shared.ComponentDelta.razor:
MyProject.Shared.ComponentEpsilon.razor:
MyProject.Shared.ComponentZeta.razor:
MyProject.Shared.ComponentEta.razor:
<div>
Stuff here ...
<MoreAndMoreComponents />
</div>
@code {
// I want to use variable "something" here as well.
}
In order to be able to share the string something
amongst all my components and embedded components, do I need to jump through all the elaborate hoops that I did for just Index.razor
, ComponentAlpha.razor
, and ComponentBeta.razor
or is there some better way?
I saw THIS out there and thought option 3. State Container would be my best bet, However, when I follow their example, I always end up with this Exception:
Error CS0119 'AppState' is a type, which is not valid in the given context
So, what is the way we are supposed to use to efficiently share data amongst all components and nested components?
Upvotes: 1
Views: 3967
Reputation: 4216
It depends how permanent you need your info to be, and to what degree you're willing to hold global variables on the parent.
1. One trick is to pass the parent control to the children-- give them full access to its variables:
ParentControl
<CascadingValue Value="this">
<ChildControl />
</CascadingValue>
ChildControl
[Parameter]
ParentControl Parent;
@code {
Parent.SomeVariable = Something;
}
2. If you don't want to do that, then you can pass data to its parent using an EventCallback<T>
ChildControl
@code
{
[Parameter]
EventCallBack<MyCustomClass> OnDataReady { get; set; }
MyCustomClass ActiveObject { get; set; }
void DoSomething()
{
OnDataReady.InvokeAsync(ActiveObject);
}
}
And on ParentControl:
<ChildControl OnDataReady=HandleData />
@code {
async Task HandleData (MyCustomClass data){
// Do stuff
}
}
3. If you REALLY want highly persistent data, then consider saving state in a database. Since Blazor requires no postbacks, there's really no penalty for saving or loading information from a database whenever you want.
4. Using a service as per @enet's answer
Upvotes: 0
Reputation: 3073
One option you can consider is using cascading parameters, which will allow a top level component to pass itself down to any child components regardless of component tree depth. It's very easy to set up.
<CascadingValue Value="this">
@*Note Component Alpha placement*@
<ComponentAlpha />
</CascadingValue>
@code {
private string something = "initial value";
// this is a getter only for the property, but you can use
// a setter for 2 way binding also. Just make sure
// to modify the setter to run 'StateHasChanged()'
// after updating the value, and then
// you can then set the property directly
public string Something => something;
// This will allow child components to set the new value
public Task SetNewValue(string value)
{
something = value;
StateHasChanged();
return Task.CompletedTask;
}
}
Note that the CascadingValue is passing this
down the tree to it's children, so you can capture it where needed.
<div>
<div @onclick="SetNewValue">@ValueFromTopLevel</div>
@*Note Component Beta placement*@
<ComponentBeta />
</div>
@code {
// Capture the top level component here
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// pull out the value you need here
public string ValueFromTopLevel => TopLevelComponent.Something;
// use this to set a new value
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Alpha component!");
}
}
Notice the beta component nested inside the alpha component.
<div>
<div @onclick="SetNewValue">@ValueFromTopLevel</div>
</div>
@code {
// still capture the top level component
[CascadingParameter]
public Component TopLevelComponent { get; set; }
// use the value the same as Alpha
public string ValueFromTopLevel => TopLevelComponent.Something;
// set the value the same as Alpha
void SetNewValue()
{
TopLevelComponent.SetNewValue("Hello from Beta component!");
}
}
With this setup, by clicking on the text of either the Alpha or the Beta component, the value of something
is updated at the top level and then the new value is cascaded down again with a fresh render. All the state management is at the top and the child components are just hooking in where needed.This setup will allow you to use methods and properties from a top level component as you see fit. For example I created a top level Toast Notification component once that wraps the whole app and can be captured and used anywhere. Simply pass it a message and a type (info, error, warning) and it displays the flash message for a few seconds and then hides again.
There are some downsides though, notably that your top level component needs to be responsible for all state management. This works for simpler scenarios, but as the app and the interactions become more complex this can get out of hand. However, if you combine this with Enet's answer and get the state management happening at the top, you can then hook in where needed down the component tree.
Also note that any child components become coupled to the top level parent as they will require being up the tree somewhere to work properly. Maybe Ok for you, maybe not. Either way, it's a good tool to be aware of.
Official docs for cascading parameters can be found here
Upvotes: 1
Reputation: 30177
You should consider using a service, probably Scoped. You then inject the service into each component, you can use an Interface and/or base abstract class to boilerplate the code. You can also use this same service for events - signalling that data has changed.
See the MS Docs here on services and how to use/provision them.
Upvotes: 1